{
 "opis": "Test promptowy.com: Claude Fable 5.2 vs Opus 5.5 vs GPT-6.1 Sol vs Gemini 4 Argon (+ Fable 5.1 w szybkości). Wszystkie wywołania przez OpenRouter 9.11.2026, reasoning effort medium (szybkość: low). Claude Mythos 5.2: brak dostępu (nie ma go w OpenRouter, wymaga Cyber/Life Sciences Verification Program).",
 "data": "2026-11-09",
 "modele": {},
 "zadania": [
  {
   "kategoria": "kod",
   "zadanie": "K1_parser",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Napisz funkcję Pythona `parse_duration(s: str) -> int`, która zamienia zapis czasu trwania na liczbę sekund.\n\nSpecyfikacja:\n- Zapis składa się z 1-4 składników. Składnik to liczba całkowita nieujemna (same cyfry) bezpośrednio przed jednostką: d (doba), h, m, s. Wielkość liter jednostki bez znaczenia.\n- Między liczbą a jednostką NIE może być spacji. Między składnikami mogą być dowolne spacje (albo żadne). Spacje na początku i końcu napisu są dozwolone.\n- Każda jednostka może wystąpić najwyżej raz, a kolejność musi być malejąca: d, potem h, potem m, potem s.\n- Liczby mogą przekraczać „naturalny” zakres (np. \"90m\" jest poprawne).\n- W każdym innym przypadku (pusty napis, nieznana jednostka, liczba bez jednostki, ułamki, znak minus, powtórzona jednostka, zła kolejność) funkcja rzuca ValueError.\n\nPrzykłady: \"1h 30m\" -> 5400, \"2h15m10s\" -> 8110, \" 1D 2H \" -> 93600.\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nimport re\n\n_DURATION_RE = re.compile(\n    r\" *(?:([0-9]+)[dD])? *(?:([0-9]+)[hH])? *(?:([0-9]+)[mM])? *(?:([0-9]+)[sS])? *\"\n)\n\n_MULTIPLIERS = (86400, 3600, 60, 1)\n\n\ndef parse_duration(s: str) -> int:\n    if not isinstance(s, str):\n        raise ValueError(\"oczekiwano napisu\")\n\n    match = _DURATION_RE.fullmatch(s)\n    if match is None:\n        raise ValueError(f\"niepoprawny zapis czasu trwania: {s!r}\")\n\n    groups = match.groups()\n    if all(g is None for g in groups):\n        raise ValueError(f\"niepoprawny zapis czasu trwania: {s!r}\")\n\n    total = 0\n    for value, multiplier in zip(groups, _MULTIPLIERS):\n        if value is not None:\n            total += int(value) * multiplier\n    return total\n```",
   "czas_s": 12.08,
   "usage": {
    "prompt_tokens": 511,
    "completion_tokens": 793,
    "total_tokens": 1304,
    "cost": 0.035808,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.035808,
     "upstream_inference_prompt_cost": 0.004088,
     "upstream_inference_completions_cost": 0.03172
    },
    "completion_tokens_details": {
     "reasoning_tokens": 447,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.035808,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.035808,
   "ocena": {
    "zaliczone": 17,
    "testy": 17,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "import re\n\n_DURATION_RE = re.compile(\n    r\" *(?:([0-9]+)[dD])? *(?:([0-9]+)[hH])? *(?:([0-9]+)[mM])? *(?:([0-9]+)[sS])? *\"\n)\n\n_MULTIPLIERS = (86400, 3600, 60, 1)\n\n\ndef parse_duration(s: str) -> int:\n    if not isinstance(s, str):\n        raise ValueError(\"oczekiwano napisu\")\n\n    match = _DURATION_RE.fullmatch(s)\n    if match is None:\n        raise ValueError(f\"niepoprawny zapis czasu trwania: {s!r}\")\n\n    groups = match.groups()\n    if all(g is None for g in groups):\n        raise ValueError(f\"niepoprawny zapis czasu trwania: {s!r}\")\n\n    total = 0\n    for value, multiplier in zip(groups, _MULTIPLIERS):\n        if value is not None:\n            total += int(value) * multiplier\n    return total\n",
   "tokeny": {
    "wej": 511,
    "wyj": 793,
    "rozumowanie": 447
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K2_algorytm",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Napisz funkcję Pythona `shortest_path(grid: list[str]) -> int`.\n\n`grid` to prostokątna plansza z napisów o równej długości. Znaki: 'S' start (dokładnie jeden), 'E' meta (dokładnie jedna), '.' wolne pole, '#' ściana.\nRuch o jedno pole w górę, dół, lewo lub prawo; każdy ruch to 1 krok. Nie wolno wyjść poza planszę.\nW całej trasie wolno wejść na najwyżej JEDNO pole '#' (przebicie ściany); wejście na nie liczy się jak zwykły krok, a potem można iść dalej.\nFunkcja zwraca minimalną liczbę kroków od S do E albo -1, jeśli się nie da. Plansza może mieć do 300x300 pól, więc rozwiązanie musi być szybkie.\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nfrom collections import deque\n\n\ndef shortest_path(grid: list[str]) -> int:\n    if not grid or not grid[0]:\n        return -1\n\n    h = len(grid)\n    w = len(grid[0])\n\n    start = end = None\n    for r in range(h):\n        row = grid[r]\n        for c in range(w):\n            ch = row[c]\n            if ch == 'S':\n                start = (r, c)\n            elif ch == 'E':\n                end = (r, c)\n\n    if start is None or end is None:\n        return -1\n    if start == end:\n        return 0\n\n    # Stan: (wiersz, kolumna, czy_przebito_już_ścianę)\n    # indeks = (r * w + c) * 2 + k\n    dist = [-1] * (h * w * 2)\n    sr, sc = start\n    er, ec = end\n    dist[(sr * w + sc) * 2] = 0\n    queue = deque()\n    queue.append((sr, sc, 0))\n\n    while queue:\n        r, c, k = queue.popleft()\n        d = dist[(r * w + c) * 2 + k] + 1\n        for nr, nc in ((r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)):\n            if nr < 0 or nr >= h or nc < 0 or nc >= w:\n                continue\n            if grid[nr][nc] == '#':\n                if k:\n                    continue\n                nk = 1\n            else:\n                nk = k\n            idx = (nr * w + nc) * 2 + nk\n            if dist[idx] != -1:\n                continue\n            if nr == er and nc == ec:\n                return d\n            dist[idx] = d\n            queue.append((nr, nc, nk))\n\n    return -1\n```",
   "czas_s": 9.43,
   "usage": {
    "prompt_tokens": 389,
    "completion_tokens": 882,
    "total_tokens": 1271,
    "cost": 0.038392,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.038392,
     "upstream_inference_prompt_cost": 0.003112,
     "upstream_inference_completions_cost": 0.03528
    },
    "completion_tokens_details": {
     "reasoning_tokens": 317,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.038392,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.038392,
   "ocena": {
    "zaliczone": 13,
    "testy": 13,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "from collections import deque\n\n\ndef shortest_path(grid: list[str]) -> int:\n    if not grid or not grid[0]:\n        return -1\n\n    h = len(grid)\n    w = len(grid[0])\n\n    start = end = None\n    for r in range(h):\n        row = grid[r]\n        for c in range(w):\n            ch = row[c]\n            if ch == 'S':\n                start = (r, c)\n            elif ch == 'E':\n                end = (r, c)\n\n    if start is None or end is None:\n        return -1\n    if start == end:\n        return 0\n\n    # Stan: (wiersz, kolumna, czy_przebito_już_ścianę)\n    # indeks = (r * w + c) * 2 + k\n    dist = [-1] * (h * w * 2)\n    sr, sc = start\n    er, ec = end\n    dist[(sr * w + sc) * 2] = 0\n    queue = deque()\n    queue.append((sr, sc, 0))\n\n    while queue:\n        r, c, k = queue.popleft()\n        d = dist[(r * w + c) * 2 + k] + 1\n        for nr, nc in ((r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)):\n            if nr < 0 or nr >= h or nc < 0 or nc >= w:\n                continue\n            if grid[nr][nc] == '#':\n                if k:\n                    continue\n                nk = 1\n            else:\n                nk = k\n            idx = (nr * w + nc) * 2 + nk\n            if dist[idx] != -1:\n                continue\n            if nr == er and nc == ec:\n                return d\n            dist[idx] = d\n            queue.append((nr, nc, nk))\n\n    return -1\n",
   "tokeny": {
    "wej": 389,
    "wyj": 882,
    "rozumowanie": 317
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K3_bugfix",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Poniższa klasa miała być cache'em LRU, ale ma błędy. Popraw ją.\n\nWymagania:\n- `LRUCache(capacity)` - capacity to liczba całkowita >= 0.\n- `get(key)` zwraca wartość albo -1, gdy klucza nie ma. Udany `get` czyni klucz „najświeżej użytym”.\n- `put(key, value)` wstawia albo aktualizuje wartość; klucz staje się najświeżej użyty. Gdy po wstawieniu NOWEGO klucza liczba elementów przekroczyłaby capacity, najpierw usuwany jest najdawniej użyty klucz. Aktualizacja istniejącego klucza nikogo nie usuwa.\n- Przy capacity == 0 nic nie jest przechowywane (put nic nie robi, get zawsze -1), bez wyjątków.\n- get i put mają działać w średnim czasie O(1).\n- Zachowaj nazwę klasy i metod.\n\n```python\nclass LRUCache:\n    def __init__(self, capacity):\n        self.capacity = capacity\n        self.data = {}\n        self.order = []\n\n    def get(self, key):\n        if key in self.data:\n            return self.data[key]\n        return -1\n\n    def put(self, key, value):\n        if len(self.data) >= self.capacity:\n            oldest = self.order.pop()\n            del self.data[oldest]\n        self.data[key] = value\n        self.order.append(key)\n```\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nfrom collections import OrderedDict\n\n\nclass LRUCache:\n    def __init__(self, capacity):\n        self.capacity = capacity\n        self.data = OrderedDict()\n\n    def get(self, key):\n        if key not in self.data:\n            return -1\n        self.data.move_to_end(key)\n        return self.data[key]\n\n    def put(self, key, value):\n        if self.capacity <= 0:\n            return\n        if key in self.data:\n            self.data[key] = value\n            self.data.move_to_end(key)\n            return\n        if len(self.data) >= self.capacity:\n            self.data.popitem(last=False)\n        self.data[key] = value\n```",
   "czas_s": 3.96,
   "usage": {
    "prompt_tokens": 581,
    "completion_tokens": 213,
    "total_tokens": 794,
    "cost": 0.013168,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.013168,
     "upstream_inference_prompt_cost": 0.004648,
     "upstream_inference_completions_cost": 0.00852
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.013168,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.013168,
   "ocena": {
    "zaliczone": 12,
    "testy": 12,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "from collections import OrderedDict\n\n\nclass LRUCache:\n    def __init__(self, capacity):\n        self.capacity = capacity\n        self.data = OrderedDict()\n\n    def get(self, key):\n        if key not in self.data:\n            return -1\n        self.data.move_to_end(key)\n        return self.data[key]\n\n    def put(self, key, value):\n        if self.capacity <= 0:\n            return\n        if key in self.data:\n            self.data[key] = value\n            self.data.move_to_end(key)\n            return\n        if len(self.data) >= self.capacity:\n            self.data.popitem(last=False)\n        self.data[key] = value\n",
   "tokeny": {
    "wej": 581,
    "wyj": 213,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K4_refaktor",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Zrefaktoryzuj poniższą funkcję: czytelne nazwy, stałe zamiast magicznych liczb, bez powtórzeń.\nWarunek twardy: zachowanie ma pozostać IDENTYCZNE dla każdego wejścia - te same wyniki co do bitu (floaty), te same wyjątki w tych samych sytuacjach i w tej samej kolejności sprawdzania. Nazwa funkcji i parametrów bez zmian.\n\n```python\ndef oblicz_fakture(pozycje, kod=None):\n    # pozycje: lista słowników {'nazwa': str, 'netto': float, 'ilosc': int, 'vat': int}\n    s=0;v=0;b=0\n    for p in pozycje:\n        if p['ilosc']<=0:\n            continue\n        n=p['netto']*p['ilosc']\n        if kod=='LATO10':\n            if p['vat']==23:\n                n=n*0.9\n        n=round(n,2)\n        if p['vat']==23:\n            x=round(n*0.23,2)\n        elif p['vat']==8:\n            x=round(n*0.08,2)\n        elif p['vat']==5:\n            x=round(n*0.05,2)\n        elif p['vat']==0:\n            x=0\n        else:\n            raise ValueError('zla stawka')\n        s=s+n\n        v=v+x\n    b=s+v\n    if kod=='STALY' and b>100:\n        b=b-5\n        s=s  # netto bez zmian\n    if kod is not None and kod not in ('LATO10','STALY'):\n        raise ValueError('zly kod')\n    return {'netto':round(s,2),'vat':round(v,2),'brutto':round(b,2)}\n```\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nSTAWKA_PODSTAWOWA = 23\nSTAWKA_ZEROWA = 0\n\n# Kolejność sprawdzania stawek jak w oryginale: 23, 8, 5 (potem 0).\nMNOZNIKI_VAT = (\n    (STAWKA_PODSTAWOWA, 0.23),\n    (8, 0.08),\n    (5, 0.05),\n)\n\nKOD_LATO = 'LATO10'\nKOD_STALY = 'STALY'\nDOZWOLONE_KODY = (KOD_LATO, KOD_STALY)\n\nMNOZNIK_RABATU_LATO = 0.9\nPROG_RABATU_STALEGO = 100\nKWOTA_RABATU_STALEGO = 5\n\nMIEJSCA_PO_PRZECINKU = 2\n\n\ndef _zaokraglij(kwota):\n    return round(kwota, MIEJSCA_PO_PRZECINKU)\n\n\ndef _kwota_vat(pozycja, netto_pozycji):\n    for stawka, mnoznik in MNOZNIKI_VAT:\n        if pozycja['vat'] == stawka:\n            return _zaokraglij(netto_pozycji * mnoznik)\n    if pozycja['vat'] == STAWKA_ZEROWA:\n        return 0  # celowo int, tak jak w oryginale\n    raise ValueError('zla stawka')\n\n\ndef oblicz_fakture(pozycje, kod=None):\n    # pozycje: lista słowników {'nazwa': str, 'netto': float, 'ilosc': int, 'vat': int}\n    suma_netto = 0\n    suma_vat = 0\n\n    for pozycja in pozycje:\n        if pozycja['ilosc'] <= 0:\n            continue\n\n        netto_pozycji = pozycja['netto'] * pozycja['ilosc']\n        if kod == KOD_LATO and pozycja['vat'] == STAWKA_PODSTAWOWA:\n            netto_pozycji = netto_pozycji * MNOZNIK_RABATU_LATO\n        netto_pozycji = _zaokraglij(netto_pozycji)\n\n        vat_pozycji = _kwota_vat(pozycja, netto_pozycji)\n\n        suma_netto = suma_netto + netto_pozycji\n        suma_vat = suma_vat + vat_pozycji\n\n    brutto = suma_netto + suma_vat\n    if kod == KOD_STALY and brutto > PROG_RABATU_STALEGO:\n        brutto = brutto - KWOTA_RABATU_STALEGO  # netto bez zmian\n\n    if kod is not None and kod not in DOZWOLONE_KODY:\n        raise ValueError('zly kod')\n\n    return {\n        'netto': _zaokraglij(suma_netto),\n        'vat': _zaokraglij(suma_vat),\n        'brutto': _zaokraglij(brutto),\n    }\n```",
   "czas_s": 25.86,
   "usage": {
    "prompt_tokens": 665,
    "completion_tokens": 2525,
    "total_tokens": 3190,
    "cost": 0.10632,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.10632,
     "upstream_inference_prompt_cost": 0.00532,
     "upstream_inference_completions_cost": 0.101
    },
    "completion_tokens_details": {
     "reasoning_tokens": 1529,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.10632,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.10632,
   "ocena": {
    "zaliczone": 16,
    "testy": 16,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "STAWKA_PODSTAWOWA = 23\nSTAWKA_ZEROWA = 0\n\n# Kolejność sprawdzania stawek jak w oryginale: 23, 8, 5 (potem 0).\nMNOZNIKI_VAT = (\n    (STAWKA_PODSTAWOWA, 0.23),\n    (8, 0.08),\n    (5, 0.05),\n)\n\nKOD_LATO = 'LATO10'\nKOD_STALY = 'STALY'\nDOZWOLONE_KODY = (KOD_LATO, KOD_STALY)\n\nMNOZNIK_RABATU_LATO = 0.9\nPROG_RABATU_STALEGO = 100\nKWOTA_RABATU_STALEGO = 5\n\nMIEJSCA_PO_PRZECINKU = 2\n\n\ndef _zaokraglij(kwota):\n    return round(kwota, MIEJSCA_PO_PRZECINKU)\n\n\ndef _kwota_vat(pozycja, netto_pozycji):\n    for stawka, mnoznik in MNOZNIKI_VAT:\n        if pozycja['vat'] == stawka:\n            return _zaokraglij(netto_pozycji * mnoznik)\n    if pozycja['vat'] == STAWKA_ZEROWA:\n        return 0  # celowo int, tak jak w oryginale\n    raise ValueError('zla stawka')\n\n\ndef oblicz_fakture(pozycje, kod=None):\n    # pozycje: lista słowników {'nazwa': str, 'netto': float, 'ilosc': int, 'vat': int}\n    suma_netto = 0\n    suma_vat = 0\n\n    for pozycja in pozycje:\n        if pozycja['ilosc'] <= 0:\n            continue\n\n        netto_pozycji = pozycja['netto'] * pozycja['ilosc']\n        if kod == KOD_LATO and pozycja['vat'] == STAWKA_PODSTAWOWA:\n            netto_pozycji = netto_pozycji * MNOZNIK_RABATU_LATO\n        netto_pozycji = _zaokraglij(netto_pozycji)\n\n        vat_pozycji = _kwota_vat(pozycja, netto_pozycji)\n\n        suma_netto = suma_netto + netto_pozycji\n        suma_vat = suma_vat + vat_pozycji\n\n    brutto = suma_netto + suma_vat\n    if kod == KOD_STALY and brutto > PROG_RABATU_STALEGO:\n        brutto = brutto - KWOTA_RABATU_STALEGO  # netto bez zmian\n\n    if kod is not None and kod not in DOZWOLONE_KODY:\n        raise ValueError('zly kod')\n\n    return {\n        'netto': _zaokraglij(suma_netto),\n        'vat': _zaokraglij(suma_vat),\n        'brutto': _zaokraglij(brutto),\n    }\n",
   "tokeny": {
    "wej": 665,
    "wyj": 2525,
    "rozumowanie": 1529
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K5_regex",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Napisz funkcję Pythona `find_dates(text: str) -> list[str]`, która znajduje w tekście daty i zwraca je jako napisy ISO 'YYYY-MM-DD' w kolejności wystąpienia (duplikaty zostają).\n\nObsługiwane formaty:\n1. DD.MM.YYYY oraz D.M.YYYY (dzień i miesiąc 1-2 cyfry, separator kropka, rok 4 cyfry), np. 12.11.2026, 1.2.2027.\n2. YYYY-MM-DD (miesiąc i dzień dokładnie 2 cyfry), np. 2027-03-15.\n3. Zapis słowny: dzień (1-2 cyfry), spacja, nazwa miesiąca w dopełniaczu (stycznia, lutego, marca, kwietnia, maja, czerwca, lipca, sierpnia, września, października, listopada, grudnia; pierwsza litera może być wielka), spacja, rok 4 cyfry, np. \"9 listopada 2026\" albo \"3 Maja 2027\". Po roku może stać „r.” lub „r”.\n\nZasady:\n- Zwracaj tylko istniejące daty kalendarzowe (uwzględnij lata przestępne): 31.02.2026 i 2026-13-01 pomijasz.\n- Data nie może być częścią dłuższej liczby: znak tuż przed dopasowaniem i tuż po nim nie może być cyfrą.\n- Użyj wyrażeń regularnych (moduł re).\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nimport re\nfrom datetime import date\n\n_MONTHS = {\n    \"stycznia\": 1,\n    \"lutego\": 2,\n    \"marca\": 3,\n    \"kwietnia\": 4,\n    \"maja\": 5,\n    \"czerwca\": 6,\n    \"lipca\": 7,\n    \"sierpnia\": 8,\n    \"września\": 9,\n    \"października\": 10,\n    \"listopada\": 11,\n    \"grudnia\": 12,\n}\n\n_MONTH_ALT = \"|\".join(\n    \"[{}{}]{}\".format(name[0].upper(), name[0], name[1:]) for name in _MONTHS\n)\n\n_DATE_RE = re.compile(\n    r\"(?<!\\d)(?:\"\n    r\"(?P<d1>[0-9]{1,2})\\.(?P<m1>[0-9]{1,2})\\.(?P<y1>[0-9]{4})\"\n    r\"|\"\n    r\"(?P<y2>[0-9]{4})-(?P<m2>[0-9]{2})-(?P<d2>[0-9]{2})\"\n    r\"|\"\n    r\"(?P<d3>[0-9]{1,2}) (?P<m3>\" + _MONTH_ALT + r\") (?P<y3>[0-9]{4})\"\n    r\")(?!\\d)\"\n)\n\n\ndef find_dates(text: str) -> list[str]:\n    results: list[str] = []\n    pos = 0\n    while True:\n        m = _DATE_RE.search(text, pos)\n        if m is None:\n            break\n\n        if m.group(\"y1\") is not None:\n            year = int(m.group(\"y1\"))\n            month = int(m.group(\"m1\"))\n            day = int(m.group(\"d1\"))\n        elif m.group(\"y2\") is not None:\n            year = int(m.group(\"y2\"))\n            month = int(m.group(\"m2\"))\n            day = int(m.group(\"d2\"))\n        else:\n            year = int(m.group(\"y3\"))\n            month = _MONTHS[m.group(\"m3\").lower()]\n            day = int(m.group(\"d3\"))\n\n        try:\n            date(year, month, day)\n        except ValueError:\n            # Niepoprawna data: szukaj dalej od następnego znaku,\n            # aby nie zgubić poprawnej daty nachodzącej na to dopasowanie.\n            pos = m.start() + 1\n            continue\n\n        results.append(\"{:04d}-{:02d}-{:02d}\".format(year, month, day))\n        pos = m.end()\n\n    return results\n```",
   "czas_s": 20.12,
   "usage": {
    "prompt_tokens": 613,
    "completion_tokens": 1861,
    "total_tokens": 2474,
    "cost": 0.079344,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.079344,
     "upstream_inference_prompt_cost": 0.004904,
     "upstream_inference_completions_cost": 0.07444
    },
    "completion_tokens_details": {
     "reasoning_tokens": 1052,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.079344,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.079344,
   "ocena": {
    "zaliczone": 13,
    "testy": 13,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "import re\nfrom datetime import date\n\n_MONTHS = {\n    \"stycznia\": 1,\n    \"lutego\": 2,\n    \"marca\": 3,\n    \"kwietnia\": 4,\n    \"maja\": 5,\n    \"czerwca\": 6,\n    \"lipca\": 7,\n    \"sierpnia\": 8,\n    \"września\": 9,\n    \"października\": 10,\n    \"listopada\": 11,\n    \"grudnia\": 12,\n}\n\n_MONTH_ALT = \"|\".join(\n    \"[{}{}]{}\".format(name[0].upper(), name[0], name[1:]) for name in _MONTHS\n)\n\n_DATE_RE = re.compile(\n    r\"(?<!\\d)(?:\"\n    r\"(?P<d1>[0-9]{1,2})\\.(?P<m1>[0-9]{1,2})\\.(?P<y1>[0-9]{4})\"\n    r\"|\"\n    r\"(?P<y2>[0-9]{4})-(?P<m2>[0-9]{2})-(?P<d2>[0-9]{2})\"\n    r\"|\"\n    r\"(?P<d3>[0-9]{1,2}) (?P<m3>\" + _MONTH_ALT + r\") (?P<y3>[0-9]{4})\"\n    r\")(?!\\d)\"\n)\n\n\ndef find_dates(text: str) -> list[str]:\n    results: list[str] = []\n    pos = 0\n    while True:\n        m = _DATE_RE.search(text, pos)\n        if m is None:\n            break\n\n        if m.group(\"y1\") is not None:\n            year = int(m.group(\"y1\"))\n            month = int(m.group(\"m1\"))\n            day = int(m.group(\"d1\"))\n        elif m.group(\"y2\") is not None:\n            year = int(m.group(\"y2\"))\n            month = int(m.group(\"m2\"))\n            day = int(m.group(\"d2\"))\n        else:\n            year = int(m.group(\"y3\"))\n            month = _MONTHS[m.group(\"m3\").lower()]\n            day = int(m.group(\"d3\"))\n\n        try:\n            date(year, month, day)\n        except ValueError:\n            # Niepoprawna data: szukaj dalej od następnego znaku,\n            # aby nie zgubić poprawnej daty nachodzącej na to dopasowanie.\n            pos = m.start() + 1\n            continue\n\n        results.append(\"{:04d}-{:02d}-{:02d}\".format(year, month, day))\n        pos = m.end()\n\n    return results\n",
   "tokeny": {
    "wej": 613,
    "wyj": 1861,
    "rozumowanie": 1052
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K6_sql",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Baza SQLite ma schemat:\n\n```sql\nCREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT NOT NULL, city TEXT);\nCREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER NOT NULL REFERENCES customers(id),\n                     order_date TEXT NOT NULL,  -- 'YYYY-MM-DD'\n                     status TEXT NOT NULL,      -- np. 'paid', 'cancelled', 'pending'\n                     discount REAL);            -- rabat kwotowy na całe zamówienie, może być NULL\nCREATE TABLE order_items (id INTEGER PRIMARY KEY, order_id INTEGER NOT NULL REFERENCES orders(id),\n                          product TEXT NOT NULL, quantity INTEGER NOT NULL, unit_price REAL NOT NULL);\n```\n\nNapisz JEDNO zapytanie SELECT, które dla każdego klienta liczy jego zamówienia ze statusem dokładnie 'paid' złożone w roku 2026 (od 2026-01-01 do 2026-12-31 włącznie).\n- Wartość zamówienia = suma quantity*unit_price jego pozycji minus discount (NULL traktuj jak 0). Zamówienie bez żadnych pozycji ma sumę pozycji 0, ale też się liczy (i też odejmujesz rabat).\n- Pokaż tylko klientów, którzy mają co najmniej 2 takie zamówienia.\n- Kolumny w tej kolejności: name, city (gdy NULL, pokaż 'brak'), orders_count, total (suma wartości zamówień zaokrąglona funkcją ROUND do 2 miejsc).\n- Sortowanie: total malejąco, potem name rosnąco.\n\nZwróć wyłącznie zapytanie w jednym bloku ```sql```.",
   "odpowiedz": "```sql\nSELECT\n    c.name,\n    COALESCE(c.city, 'brak') AS city,\n    COUNT(*) AS orders_count,\n    ROUND(SUM(o.order_value), 2) AS total\nFROM customers AS c\nJOIN (\n    SELECT\n        ord.id,\n        ord.customer_id,\n        COALESCE(SUM(oi.quantity * oi.unit_price), 0) - COALESCE(ord.discount, 0) AS order_value\n    FROM orders AS ord\n    LEFT JOIN order_items AS oi ON oi.order_id = ord.id\n    WHERE ord.status = 'paid'\n      AND ord.order_date >= '2026-01-01'\n      AND ord.order_date <= '2026-12-31'\n    GROUP BY ord.id, ord.customer_id, ord.discount\n) AS o ON o.customer_id = c.id\nGROUP BY c.id, c.name, c.city\nHAVING COUNT(*) >= 2\nORDER BY total DESC, c.name ASC;\n```",
   "czas_s": 4.06,
   "usage": {
    "prompt_tokens": 675,
    "completion_tokens": 360,
    "total_tokens": 1035,
    "cost": 0.0198,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.0198,
     "upstream_inference_prompt_cost": 0.0054,
     "upstream_inference_completions_cost": 0.0144
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.0198,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.0198,
   "ocena": {
    "zaliczone": 5,
    "testy": 5,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "SELECT\n    c.name,\n    COALESCE(c.city, 'brak') AS city,\n    COUNT(*) AS orders_count,\n    ROUND(SUM(o.order_value), 2) AS total\nFROM customers AS c\nJOIN (\n    SELECT\n        ord.id,\n        ord.customer_id,\n        COALESCE(SUM(oi.quantity * oi.unit_price), 0) - COALESCE(ord.discount, 0) AS order_value\n    FROM orders AS ord\n    LEFT JOIN order_items AS oi ON oi.order_id = ord.id\n    WHERE ord.status = 'paid'\n      AND ord.order_date >= '2026-01-01'\n      AND ord.order_date <= '2026-12-31'\n    GROUP BY ord.id, ord.customer_id, ord.discount\n) AS o ON o.customer_id = c.id\nGROUP BY c.id, c.name, c.city\nHAVING COUNT(*) >= 2\nORDER BY total DESC, c.name ASC",
   "tokeny": {
    "wej": 675,
    "wyj": 360,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K1_parser",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Napisz funkcję Pythona `parse_duration(s: str) -> int`, która zamienia zapis czasu trwania na liczbę sekund.\n\nSpecyfikacja:\n- Zapis składa się z 1-4 składników. Składnik to liczba całkowita nieujemna (same cyfry) bezpośrednio przed jednostką: d (doba), h, m, s. Wielkość liter jednostki bez znaczenia.\n- Między liczbą a jednostką NIE może być spacji. Między składnikami mogą być dowolne spacje (albo żadne). Spacje na początku i końcu napisu są dozwolone.\n- Każda jednostka może wystąpić najwyżej raz, a kolejność musi być malejąca: d, potem h, potem m, potem s.\n- Liczby mogą przekraczać „naturalny” zakres (np. \"90m\" jest poprawne).\n- W każdym innym przypadku (pusty napis, nieznana jednostka, liczba bez jednostki, ułamki, znak minus, powtórzona jednostka, zła kolejność) funkcja rzuca ValueError.\n\nPrzykłady: \"1h 30m\" -> 5400, \"2h15m10s\" -> 8110, \" 1D 2H \" -> 93600.\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nimport re\n\n_DURATION_PATTERN = re.compile(\n    r\"^\\s*(?:(\\d+)d)?\\s*(?:(\\d+)h)?\\s*(?:(\\d+)m)?\\s*(?:(\\d+)s)?\\s*$\",\n    re.IGNORECASE | re.ASCII,\n)\n\n\ndef parse_duration(s: str) -> int:\n    if not isinstance(s, str):\n        raise ValueError(\"Input must be a string\")\n\n    match = _DURATION_PATTERN.match(s)\n    if not match or not any(match.groups()):\n        raise ValueError(f\"Invalid duration format: {s!r}\")\n\n    d, h, m, sec = (int(val) if val is not None else 0 for val in match.groups())\n    return d * 86400 + h * 3600 + m * 60 + sec\n```",
   "czas_s": 6.4,
   "usage": {
    "prompt_tokens": 364,
    "completion_tokens": 212,
    "total_tokens": 576,
    "cost": 0.002848,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002848,
     "upstream_inference_prompt_cost": 0.000728,
     "upstream_inference_completions_cost": 0.00212
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002848,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002848,
   "ocena": {
    "zaliczone": 17,
    "testy": 17,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "import re\n\n_DURATION_PATTERN = re.compile(\n    r\"^\\s*(?:(\\d+)d)?\\s*(?:(\\d+)h)?\\s*(?:(\\d+)m)?\\s*(?:(\\d+)s)?\\s*$\",\n    re.IGNORECASE | re.ASCII,\n)\n\n\ndef parse_duration(s: str) -> int:\n    if not isinstance(s, str):\n        raise ValueError(\"Input must be a string\")\n\n    match = _DURATION_PATTERN.match(s)\n    if not match or not any(match.groups()):\n        raise ValueError(f\"Invalid duration format: {s!r}\")\n\n    d, h, m, sec = (int(val) if val is not None else 0 for val in match.groups())\n    return d * 86400 + h * 3600 + m * 60 + sec\n",
   "tokeny": {
    "wej": 364,
    "wyj": 212,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K1_parser",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Napisz funkcję Pythona `parse_duration(s: str) -> int`, która zamienia zapis czasu trwania na liczbę sekund.\n\nSpecyfikacja:\n- Zapis składa się z 1-4 składników. Składnik to liczba całkowita nieujemna (same cyfry) bezpośrednio przed jednostką: d (doba), h, m, s. Wielkość liter jednostki bez znaczenia.\n- Między liczbą a jednostką NIE może być spacji. Między składnikami mogą być dowolne spacje (albo żadne). Spacje na początku i końcu napisu są dozwolone.\n- Każda jednostka może wystąpić najwyżej raz, a kolejność musi być malejąca: d, potem h, potem m, potem s.\n- Liczby mogą przekraczać „naturalny” zakres (np. \"90m\" jest poprawne).\n- W każdym innym przypadku (pusty napis, nieznana jednostka, liczba bez jednostki, ułamki, znak minus, powtórzona jednostka, zła kolejność) funkcja rzuca ValueError.\n\nPrzykłady: \"1h 30m\" -> 5400, \"2h15m10s\" -> 8110, \" 1D 2H \" -> 93600.\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nimport re\n\n\ndef parse_duration(s: str) -> int:\n    s = s.strip()\n    if not s:\n        raise ValueError(\"Pusty zapis czasu trwania\")\n\n    pattern = re.compile(r\"([0-9]+)([dDhHmMsS])\\s*\")\n    units = {\n        \"d\": (0, 86400),\n        \"h\": (1, 3600),\n        \"m\": (2, 60),\n        \"s\": (3, 1),\n    }\n\n    total = 0\n    position = 0\n    previous_order = -1\n\n    while position < len(s):\n        match = pattern.match(s, position)\n        if match is None:\n            raise ValueError(\"Nieprawidłowy zapis czasu trwania\")\n\n        digits, unit = match.groups()\n        order, multiplier = units[unit.lower()]\n        if order <= previous_order:\n            raise ValueError(\"Powtórzona jednostka lub nieprawidłowa kolejność\")\n\n        # Konwersja porcjami obsługuje również bardzo długie liczby.\n        value = 0\n        for start in range(0, len(digits), 9):\n            chunk = digits[start:start + 9]\n            value = value * (10 ** len(chunk)) + int(chunk)\n\n        total += value * multiplier\n        previous_order = order\n        position = match.end()\n\n    return total\n```",
   "czas_s": 8.6,
   "usage": {
    "prompt_tokens": 347,
    "completion_tokens": 472,
    "total_tokens": 819,
    "cost": 0.005414,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.005414,
     "upstream_inference_prompt_cost": 0.000694,
     "upstream_inference_completions_cost": 0.00472
    },
    "completion_tokens_details": {
     "reasoning_tokens": 161,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.005414,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.005414,
   "ocena": {
    "zaliczone": 17,
    "testy": 17,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "import re\n\n\ndef parse_duration(s: str) -> int:\n    s = s.strip()\n    if not s:\n        raise ValueError(\"Pusty zapis czasu trwania\")\n\n    pattern = re.compile(r\"([0-9]+)([dDhHmMsS])\\s*\")\n    units = {\n        \"d\": (0, 86400),\n        \"h\": (1, 3600),\n        \"m\": (2, 60),\n        \"s\": (3, 1),\n    }\n\n    total = 0\n    position = 0\n    previous_order = -1\n\n    while position < len(s):\n        match = pattern.match(s, position)\n        if match is None:\n            raise ValueError(\"Nieprawidłowy zapis czasu trwania\")\n\n        digits, unit = match.groups()\n        order, multiplier = units[unit.lower()]\n        if order <= previous_order:\n            raise ValueError(\"Powtórzona jednostka lub nieprawidłowa kolejność\")\n\n        # Konwersja porcjami obsługuje również bardzo długie liczby.\n        value = 0\n        for start in range(0, len(digits), 9):\n            chunk = digits[start:start + 9]\n            value = value * (10 ** len(chunk)) + int(chunk)\n\n        total += value * multiplier\n        previous_order = order\n        position = match.end()\n\n    return total\n",
   "tokeny": {
    "wej": 347,
    "wyj": 472,
    "rozumowanie": 161
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K1_parser",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Napisz funkcję Pythona `parse_duration(s: str) -> int`, która zamienia zapis czasu trwania na liczbę sekund.\n\nSpecyfikacja:\n- Zapis składa się z 1-4 składników. Składnik to liczba całkowita nieujemna (same cyfry) bezpośrednio przed jednostką: d (doba), h, m, s. Wielkość liter jednostki bez znaczenia.\n- Między liczbą a jednostką NIE może być spacji. Między składnikami mogą być dowolne spacje (albo żadne). Spacje na początku i końcu napisu są dozwolone.\n- Każda jednostka może wystąpić najwyżej raz, a kolejność musi być malejąca: d, potem h, potem m, potem s.\n- Liczby mogą przekraczać „naturalny” zakres (np. \"90m\" jest poprawne).\n- W każdym innym przypadku (pusty napis, nieznana jednostka, liczba bez jednostki, ułamki, znak minus, powtórzona jednostka, zła kolejność) funkcja rzuca ValueError.\n\nPrzykłady: \"1h 30m\" -> 5400, \"2h15m10s\" -> 8110, \" 1D 2H \" -> 93600.\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nimport re\n\n_TOKEN = re.compile(r\"([0-9]+)([dhmsDHMS]) *\")\n_FACTORS = {\"d\": 86400, \"h\": 3600, \"m\": 60, \"s\": 1}\n_ORDER = {\"d\": 0, \"h\": 1, \"m\": 2, \"s\": 3}\n\n\ndef parse_duration(s: str) -> int:\n    if not isinstance(s, str):\n        raise ValueError(\"duration must be a string\")\n\n    # Pomijamy spacje na początku napisu.\n    pos = 0\n    n = len(s)\n    while pos < n and s[pos] == \" \":\n        pos += 1\n\n    if pos == n:\n        raise ValueError(\"empty duration\")\n\n    total = 0\n    last_rank = -1\n    count = 0\n\n    while pos < n:\n        m = _TOKEN.match(s, pos)\n        if m is None:\n            raise ValueError(f\"invalid duration: {s!r}\")\n        number, unit = m.group(1), m.group(2).lower()\n        rank = _ORDER[unit]\n        if rank <= last_rank:\n            raise ValueError(f\"repeated or out-of-order unit in: {s!r}\")\n        last_rank = rank\n        total += int(number) * _FACTORS[unit]\n        count += 1\n        pos = m.end()\n\n    if count == 0 or count > 4:\n        raise ValueError(f\"invalid duration: {s!r}\")\n\n    return total\n```",
   "czas_s": 11.52,
   "usage": {
    "prompt_tokens": 511,
    "completion_tokens": 917,
    "total_tokens": 1428,
    "cost": 0.020384,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.020384,
     "upstream_inference_prompt_cost": 0.002044,
     "upstream_inference_completions_cost": 0.01834
    },
    "completion_tokens_details": {
     "reasoning_tokens": 442,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.020384,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.020384,
   "ocena": {
    "zaliczone": 17,
    "testy": 17,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "import re\n\n_TOKEN = re.compile(r\"([0-9]+)([dhmsDHMS]) *\")\n_FACTORS = {\"d\": 86400, \"h\": 3600, \"m\": 60, \"s\": 1}\n_ORDER = {\"d\": 0, \"h\": 1, \"m\": 2, \"s\": 3}\n\n\ndef parse_duration(s: str) -> int:\n    if not isinstance(s, str):\n        raise ValueError(\"duration must be a string\")\n\n    # Pomijamy spacje na początku napisu.\n    pos = 0\n    n = len(s)\n    while pos < n and s[pos] == \" \":\n        pos += 1\n\n    if pos == n:\n        raise ValueError(\"empty duration\")\n\n    total = 0\n    last_rank = -1\n    count = 0\n\n    while pos < n:\n        m = _TOKEN.match(s, pos)\n        if m is None:\n            raise ValueError(f\"invalid duration: {s!r}\")\n        number, unit = m.group(1), m.group(2).lower()\n        rank = _ORDER[unit]\n        if rank <= last_rank:\n            raise ValueError(f\"repeated or out-of-order unit in: {s!r}\")\n        last_rank = rank\n        total += int(number) * _FACTORS[unit]\n        count += 1\n        pos = m.end()\n\n    if count == 0 or count > 4:\n        raise ValueError(f\"invalid duration: {s!r}\")\n\n    return total\n",
   "tokeny": {
    "wej": 511,
    "wyj": 917,
    "rozumowanie": 442
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K2_algorytm",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Napisz funkcję Pythona `shortest_path(grid: list[str]) -> int`.\n\n`grid` to prostokątna plansza z napisów o równej długości. Znaki: 'S' start (dokładnie jeden), 'E' meta (dokładnie jedna), '.' wolne pole, '#' ściana.\nRuch o jedno pole w górę, dół, lewo lub prawo; każdy ruch to 1 krok. Nie wolno wyjść poza planszę.\nW całej trasie wolno wejść na najwyżej JEDNO pole '#' (przebicie ściany); wejście na nie liczy się jak zwykły krok, a potem można iść dalej.\nFunkcja zwraca minimalną liczbę kroków od S do E albo -1, jeśli się nie da. Plansza może mieć do 300x300 pól, więc rozwiązanie musi być szybkie.\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nfrom collections import deque\n\n\ndef shortest_path(grid: list[str]) -> int:\n    rows = len(grid)\n    cols = len(grid[0])\n\n    start_r, start_c = 0, 0\n    for r in range(rows):\n        for c in range(cols):\n            if grid[r][c] == \"S\":\n                start_r, start_c = r, c\n                break\n\n    # Kolejka przechowuje krotki: (wiersz, kolumna, czy_uzyto_przebicia, dystans)\n    queue = deque([(start_r, start_c, 0, 0)])\n\n    # visited[r][c][0] - odwiedzono bez przebijania ściany\n    # visited[r][c][1] - odwiedzono po przebiciu ściany\n    visited = [[[False, False] for _ in range(cols)] for _ in range(rows)]\n    visited[start_r][start_c][0] = True\n\n    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]\n\n    while queue:\n        r, c, broken, dist = queue.popleft()\n\n        if grid[r][c] == \"E\":\n            return dist\n\n        for dr, dc in directions:\n            nr, nc = r + dr, c + dc\n\n            if 0 <= nr < rows and 0 <= nc < cols:\n                cell = grid[nr][nc]\n\n                if cell == \"#\":\n                    if broken == 0 and not visited[nr][nc][1]:\n                        visited[nr][nc][1] = True\n                        queue.append((nr, nc, 1, dist + 1))\n                else:\n                    if not visited[nr][nc][broken]:\n                        visited[nr][nc][broken] = True\n                        queue.append((nr, nc, broken, dist + 1))\n\n    return -1\n```",
   "czas_s": 8.29,
   "usage": {
    "prompt_tokens": 267,
    "completion_tokens": 461,
    "total_tokens": 728,
    "cost": 0.005144,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.005144,
     "upstream_inference_prompt_cost": 0.000534,
     "upstream_inference_completions_cost": 0.00461
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.005144,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.005144,
   "ocena": {
    "zaliczone": 13,
    "testy": 13,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "from collections import deque\n\n\ndef shortest_path(grid: list[str]) -> int:\n    rows = len(grid)\n    cols = len(grid[0])\n\n    start_r, start_c = 0, 0\n    for r in range(rows):\n        for c in range(cols):\n            if grid[r][c] == \"S\":\n                start_r, start_c = r, c\n                break\n\n    # Kolejka przechowuje krotki: (wiersz, kolumna, czy_uzyto_przebicia, dystans)\n    queue = deque([(start_r, start_c, 0, 0)])\n\n    # visited[r][c][0] - odwiedzono bez przebijania ściany\n    # visited[r][c][1] - odwiedzono po przebiciu ściany\n    visited = [[[False, False] for _ in range(cols)] for _ in range(rows)]\n    visited[start_r][start_c][0] = True\n\n    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]\n\n    while queue:\n        r, c, broken, dist = queue.popleft()\n\n        if grid[r][c] == \"E\":\n            return dist\n\n        for dr, dc in directions:\n            nr, nc = r + dr, c + dc\n\n            if 0 <= nr < rows and 0 <= nc < cols:\n                cell = grid[nr][nc]\n\n                if cell == \"#\":\n                    if broken == 0 and not visited[nr][nc][1]:\n                        visited[nr][nc][1] = True\n                        queue.append((nr, nc, 1, dist + 1))\n                else:\n                    if not visited[nr][nc][broken]:\n                        visited[nr][nc][broken] = True\n                        queue.append((nr, nc, broken, dist + 1))\n\n    return -1\n",
   "tokeny": {
    "wej": 267,
    "wyj": 461,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K3_bugfix",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Poniższa klasa miała być cache'em LRU, ale ma błędy. Popraw ją.\n\nWymagania:\n- `LRUCache(capacity)` - capacity to liczba całkowita >= 0.\n- `get(key)` zwraca wartość albo -1, gdy klucza nie ma. Udany `get` czyni klucz „najświeżej użytym”.\n- `put(key, value)` wstawia albo aktualizuje wartość; klucz staje się najświeżej użyty. Gdy po wstawieniu NOWEGO klucza liczba elementów przekroczyłaby capacity, najpierw usuwany jest najdawniej użyty klucz. Aktualizacja istniejącego klucza nikogo nie usuwa.\n- Przy capacity == 0 nic nie jest przechowywane (put nic nie robi, get zawsze -1), bez wyjątków.\n- get i put mają działać w średnim czasie O(1).\n- Zachowaj nazwę klasy i metod.\n\n```python\nclass LRUCache:\n    def __init__(self, capacity):\n        self.capacity = capacity\n        self.data = {}\n        self.order = []\n\n    def get(self, key):\n        if key in self.data:\n            return self.data[key]\n        return -1\n\n    def put(self, key, value):\n        if len(self.data) >= self.capacity:\n            oldest = self.order.pop()\n            del self.data[oldest]\n        self.data[key] = value\n        self.order.append(key)\n```\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nfrom collections import OrderedDict\n\n\nclass LRUCache:\n\n    def __init__(self, capacity):\n        self.capacity = capacity\n        self.data = OrderedDict()\n\n    def get(self, key):\n        if key not in self.data:\n            return -1\n        self.data.move_to_end(key)\n        return self.data[key]\n\n    def put(self, key, value):\n        if self.capacity <= 0:\n            return\n        if key in self.data:\n            self.data.move_to_end(key)\n            self.data[key] = value\n        else:\n            if len(self.data) >= self.capacity:\n                self.data.popitem(last=False)\n            self.data[key] = value\n```",
   "czas_s": 4.29,
   "usage": {
    "prompt_tokens": 419,
    "completion_tokens": 191,
    "total_tokens": 610,
    "cost": 0.002748,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002748,
     "upstream_inference_prompt_cost": 0.000838,
     "upstream_inference_completions_cost": 0.00191
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002748,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002748,
   "ocena": {
    "zaliczone": 12,
    "testy": 12,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "from collections import OrderedDict\n\n\nclass LRUCache:\n\n    def __init__(self, capacity):\n        self.capacity = capacity\n        self.data = OrderedDict()\n\n    def get(self, key):\n        if key not in self.data:\n            return -1\n        self.data.move_to_end(key)\n        return self.data[key]\n\n    def put(self, key, value):\n        if self.capacity <= 0:\n            return\n        if key in self.data:\n            self.data.move_to_end(key)\n            self.data[key] = value\n        else:\n            if len(self.data) >= self.capacity:\n                self.data.popitem(last=False)\n            self.data[key] = value\n",
   "tokeny": {
    "wej": 419,
    "wyj": 191,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K2_algorytm",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Napisz funkcję Pythona `shortest_path(grid: list[str]) -> int`.\n\n`grid` to prostokątna plansza z napisów o równej długości. Znaki: 'S' start (dokładnie jeden), 'E' meta (dokładnie jedna), '.' wolne pole, '#' ściana.\nRuch o jedno pole w górę, dół, lewo lub prawo; każdy ruch to 1 krok. Nie wolno wyjść poza planszę.\nW całej trasie wolno wejść na najwyżej JEDNO pole '#' (przebicie ściany); wejście na nie liczy się jak zwykły krok, a potem można iść dalej.\nFunkcja zwraca minimalną liczbę kroków od S do E albo -1, jeśli się nie da. Plansza może mieć do 300x300 pól, więc rozwiązanie musi być szybkie.\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nfrom collections import deque\n\n\ndef shortest_path(grid: list[str]) -> int:\n    if not grid or not grid[0]:\n        return -1\n\n    rows = len(grid)\n    cols = len(grid[0])\n    n = rows * cols\n\n    # Flatten the grid into a single string for fast indexing\n    flat = \"\".join(grid)\n    start = flat.find('S')\n    end = flat.find('E')\n    if start == -1 or end == -1:\n        return -1\n    if start == end:\n        return 0\n\n    is_wall = [ch == '#' for ch in flat]\n\n    # dist0: distance without having broken a wall\n    # dist1: distance after breaking exactly one wall\n    dist0 = [-1] * n\n    dist1 = [-1] * n\n    dist0[start] = 0\n\n    queue = deque()\n    queue.append((start, 0))\n\n    while queue:\n        idx, used = queue.popleft()\n        if used:\n            d = dist1[idx]\n        else:\n            d = dist0[idx]\n\n        if idx == end:\n            return d\n\n        r, c = divmod(idx, cols)\n        nd = d + 1\n\n        # Generate neighbours: up, down, left, right\n        neighbours = []\n        if r > 0:\n            neighbours.append(idx - cols)\n        if r < rows - 1:\n            neighbours.append(idx + cols)\n        if c > 0:\n            neighbours.append(idx - 1)\n        if c < cols - 1:\n            neighbours.append(idx + 1)\n\n        for nb in neighbours:\n            if is_wall[nb]:\n                if used:\n                    continue\n                # Break the wall: transition to layer 1\n                if dist1[nb] == -1:\n                    dist1[nb] = nd\n                    queue.append((nb, 1))\n            else:\n                if used:\n                    if dist1[nb] == -1:\n                        dist1[nb] = nd\n                        queue.append((nb, 1))\n                else:\n                    if dist0[nb] == -1:\n                        dist0[nb] = nd\n                        queue.append((nb, 0))\n\n    return -1\n```",
   "czas_s": 8.63,
   "usage": {
    "prompt_tokens": 389,
    "completion_tokens": 874,
    "total_tokens": 1263,
    "cost": 0.019036,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.019036,
     "upstream_inference_prompt_cost": 0.001556,
     "upstream_inference_completions_cost": 0.01748
    },
    "completion_tokens_details": {
     "reasoning_tokens": 189,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.019036,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.019036,
   "ocena": {
    "zaliczone": 13,
    "testy": 13,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "from collections import deque\n\n\ndef shortest_path(grid: list[str]) -> int:\n    if not grid or not grid[0]:\n        return -1\n\n    rows = len(grid)\n    cols = len(grid[0])\n    n = rows * cols\n\n    # Flatten the grid into a single string for fast indexing\n    flat = \"\".join(grid)\n    start = flat.find('S')\n    end = flat.find('E')\n    if start == -1 or end == -1:\n        return -1\n    if start == end:\n        return 0\n\n    is_wall = [ch == '#' for ch in flat]\n\n    # dist0: distance without having broken a wall\n    # dist1: distance after breaking exactly one wall\n    dist0 = [-1] * n\n    dist1 = [-1] * n\n    dist0[start] = 0\n\n    queue = deque()\n    queue.append((start, 0))\n\n    while queue:\n        idx, used = queue.popleft()\n        if used:\n            d = dist1[idx]\n        else:\n            d = dist0[idx]\n\n        if idx == end:\n            return d\n\n        r, c = divmod(idx, cols)\n        nd = d + 1\n\n        # Generate neighbours: up, down, left, right\n        neighbours = []\n        if r > 0:\n            neighbours.append(idx - cols)\n        if r < rows - 1:\n            neighbours.append(idx + cols)\n        if c > 0:\n            neighbours.append(idx - 1)\n        if c < cols - 1:\n            neighbours.append(idx + 1)\n\n        for nb in neighbours:\n            if is_wall[nb]:\n                if used:\n                    continue\n                # Break the wall: transition to layer 1\n                if dist1[nb] == -1:\n                    dist1[nb] = nd\n                    queue.append((nb, 1))\n            else:\n                if used:\n                    if dist1[nb] == -1:\n                        dist1[nb] = nd\n                        queue.append((nb, 1))\n                else:\n                    if dist0[nb] == -1:\n                        dist0[nb] = nd\n                        queue.append((nb, 0))\n\n    return -1\n",
   "tokeny": {
    "wej": 389,
    "wyj": 874,
    "rozumowanie": 189
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K3_bugfix",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Poniższa klasa miała być cache'em LRU, ale ma błędy. Popraw ją.\n\nWymagania:\n- `LRUCache(capacity)` - capacity to liczba całkowita >= 0.\n- `get(key)` zwraca wartość albo -1, gdy klucza nie ma. Udany `get` czyni klucz „najświeżej użytym”.\n- `put(key, value)` wstawia albo aktualizuje wartość; klucz staje się najświeżej użyty. Gdy po wstawieniu NOWEGO klucza liczba elementów przekroczyłaby capacity, najpierw usuwany jest najdawniej użyty klucz. Aktualizacja istniejącego klucza nikogo nie usuwa.\n- Przy capacity == 0 nic nie jest przechowywane (put nic nie robi, get zawsze -1), bez wyjątków.\n- get i put mają działać w średnim czasie O(1).\n- Zachowaj nazwę klasy i metod.\n\n```python\nclass LRUCache:\n    def __init__(self, capacity):\n        self.capacity = capacity\n        self.data = {}\n        self.order = []\n\n    def get(self, key):\n        if key in self.data:\n            return self.data[key]\n        return -1\n\n    def put(self, key, value):\n        if len(self.data) >= self.capacity:\n            oldest = self.order.pop()\n            del self.data[oldest]\n        self.data[key] = value\n        self.order.append(key)\n```\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nfrom collections import OrderedDict\n\n\nclass LRUCache:\n    def __init__(self, capacity):\n        self.capacity = max(0, int(capacity))\n        self.data = OrderedDict()\n\n    def get(self, key):\n        if key not in self.data:\n            return -1\n        self.data.move_to_end(key)\n        return self.data[key]\n\n    def put(self, key, value):\n        if self.capacity <= 0:\n            return\n        if key in self.data:\n            self.data[key] = value\n            self.data.move_to_end(key)\n            return\n        if len(self.data) >= self.capacity:\n            self.data.popitem(last=False)\n        self.data[key] = value\n```",
   "czas_s": 2.82,
   "usage": {
    "prompt_tokens": 581,
    "completion_tokens": 220,
    "total_tokens": 801,
    "cost": 0.006724,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.006724,
     "upstream_inference_prompt_cost": 0.002324,
     "upstream_inference_completions_cost": 0.0044
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.006724,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.006724,
   "ocena": {
    "zaliczone": 12,
    "testy": 12,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "from collections import OrderedDict\n\n\nclass LRUCache:\n    def __init__(self, capacity):\n        self.capacity = max(0, int(capacity))\n        self.data = OrderedDict()\n\n    def get(self, key):\n        if key not in self.data:\n            return -1\n        self.data.move_to_end(key)\n        return self.data[key]\n\n    def put(self, key, value):\n        if self.capacity <= 0:\n            return\n        if key in self.data:\n            self.data[key] = value\n            self.data.move_to_end(key)\n            return\n        if len(self.data) >= self.capacity:\n            self.data.popitem(last=False)\n        self.data[key] = value\n",
   "tokeny": {
    "wej": 581,
    "wyj": 220,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K2_algorytm",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Napisz funkcję Pythona `shortest_path(grid: list[str]) -> int`.\n\n`grid` to prostokątna plansza z napisów o równej długości. Znaki: 'S' start (dokładnie jeden), 'E' meta (dokładnie jedna), '.' wolne pole, '#' ściana.\nRuch o jedno pole w górę, dół, lewo lub prawo; każdy ruch to 1 krok. Nie wolno wyjść poza planszę.\nW całej trasie wolno wejść na najwyżej JEDNO pole '#' (przebicie ściany); wejście na nie liczy się jak zwykły krok, a potem można iść dalej.\nFunkcja zwraca minimalną liczbę kroków od S do E albo -1, jeśli się nie da. Plansza może mieć do 300x300 pól, więc rozwiązanie musi być szybkie.\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nfrom collections import deque\n\n\ndef shortest_path(grid: list[str]) -> int:\n    if not grid or not grid[0]:\n        return -1\n\n    stride = len(grid[0]) + 2\n    border = \"!\" * stride\n    board = (\n        border\n        + \"\".join(\"!\" + row + \"!\" for row in grid)\n        + border\n    ).encode(\"ascii\")\n\n    start = board.index(b\"S\")\n    end = board.index(b\"E\")\n\n    # Stan: 2 * pozycja + informacja, czy przebito już ścianę.\n    visited = bytearray(2 * len(board))\n    initial_state = 2 * start\n    visited[initial_state] = 1\n    queue = deque([initial_state])\n\n    steps = 0\n    offsets = (-stride, stride, -1, 1)\n\n    while queue:\n        for _ in range(len(queue)):\n            state = queue.popleft()\n            position = state >> 1\n            used = state & 1\n\n            if position == end:\n                return steps\n\n            for offset in offsets:\n                neighbor = position + offset\n                cell = board[neighbor]\n\n                if cell == 33:  # '!' — poza planszą\n                    continue\n\n                next_used = used\n                if cell == 35:  # '#'\n                    if used:\n                        continue\n                    next_used = 1\n\n                next_state = (neighbor << 1) | next_used\n                if not visited[next_state]:\n                    visited[next_state] = 1\n                    queue.append(next_state)\n\n        steps += 1\n\n    return -1\n```",
   "czas_s": 17.73,
   "usage": {
    "prompt_tokens": 253,
    "completion_tokens": 730,
    "total_tokens": 983,
    "cost": 0.007806,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.007806,
     "upstream_inference_prompt_cost": 0.000506,
     "upstream_inference_completions_cost": 0.0073
    },
    "completion_tokens_details": {
     "reasoning_tokens": 377,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.007806,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.007806,
   "ocena": {
    "zaliczone": 13,
    "testy": 13,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "from collections import deque\n\n\ndef shortest_path(grid: list[str]) -> int:\n    if not grid or not grid[0]:\n        return -1\n\n    stride = len(grid[0]) + 2\n    border = \"!\" * stride\n    board = (\n        border\n        + \"\".join(\"!\" + row + \"!\" for row in grid)\n        + border\n    ).encode(\"ascii\")\n\n    start = board.index(b\"S\")\n    end = board.index(b\"E\")\n\n    # Stan: 2 * pozycja + informacja, czy przebito już ścianę.\n    visited = bytearray(2 * len(board))\n    initial_state = 2 * start\n    visited[initial_state] = 1\n    queue = deque([initial_state])\n\n    steps = 0\n    offsets = (-stride, stride, -1, 1)\n\n    while queue:\n        for _ in range(len(queue)):\n            state = queue.popleft()\n            position = state >> 1\n            used = state & 1\n\n            if position == end:\n                return steps\n\n            for offset in offsets:\n                neighbor = position + offset\n                cell = board[neighbor]\n\n                if cell == 33:  # '!' — poza planszą\n                    continue\n\n                next_used = used\n                if cell == 35:  # '#'\n                    if used:\n                        continue\n                    next_used = 1\n\n                next_state = (neighbor << 1) | next_used\n                if not visited[next_state]:\n                    visited[next_state] = 1\n                    queue.append(next_state)\n\n        steps += 1\n\n    return -1\n",
   "tokeny": {
    "wej": 253,
    "wyj": 730,
    "rozumowanie": 377
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K4_refaktor",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Zrefaktoryzuj poniższą funkcję: czytelne nazwy, stałe zamiast magicznych liczb, bez powtórzeń.\nWarunek twardy: zachowanie ma pozostać IDENTYCZNE dla każdego wejścia - te same wyniki co do bitu (floaty), te same wyjątki w tych samych sytuacjach i w tej samej kolejności sprawdzania. Nazwa funkcji i parametrów bez zmian.\n\n```python\ndef oblicz_fakture(pozycje, kod=None):\n    # pozycje: lista słowników {'nazwa': str, 'netto': float, 'ilosc': int, 'vat': int}\n    s=0;v=0;b=0\n    for p in pozycje:\n        if p['ilosc']<=0:\n            continue\n        n=p['netto']*p['ilosc']\n        if kod=='LATO10':\n            if p['vat']==23:\n                n=n*0.9\n        n=round(n,2)\n        if p['vat']==23:\n            x=round(n*0.23,2)\n        elif p['vat']==8:\n            x=round(n*0.08,2)\n        elif p['vat']==5:\n            x=round(n*0.05,2)\n        elif p['vat']==0:\n            x=0\n        else:\n            raise ValueError('zla stawka')\n        s=s+n\n        v=v+x\n    b=s+v\n    if kod=='STALY' and b>100:\n        b=b-5\n        s=s  # netto bez zmian\n    if kod is not None and kod not in ('LATO10','STALY'):\n        raise ValueError('zly kod')\n    return {'netto':round(s,2),'vat':round(v,2),'brutto':round(b,2)}\n```\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nKOD_LATO = 'LATO10'\nKOD_STALY = 'STALY'\nDOZWOLONE_KODY = (KOD_LATO, KOD_STALY)\n\nVAT_PODSTAWOWY = 23\nMNOZNIK_RABATU_LATO = 0.9\n\nPROG_RABATU_STALY = 100\nKWOTA_RABATU_STALY = 5\n\nPRECYZJA_KWOT = 2\n\nMNOZNIKI_VAT = {\n    23: 0.23,\n    8: 0.08,\n    5: 0.05,\n}\nSTAWKA_VAT_ZERO = 0\n\nBLAD_STAWKI = 'zla stawka'\nBLAD_KODU = 'zly kod'\n\n\ndef oblicz_fakture(pozycje, kod=None):\n    # pozycje: lista słowników {'nazwa': str, 'netto': float, 'ilosc': int, 'vat': int}\n    suma_netto = 0\n    suma_vat = 0\n    suma_brutto = 0\n\n    for pozycja in pozycje:\n        if pozycja['ilosc'] <= 0:\n            continue\n\n        wartosc_netto = pozycja['netto'] * pozycja['ilosc']\n\n        if kod == KOD_LATO:\n            if pozycja['vat'] == VAT_PODSTAWOWY:\n                wartosc_netto = wartosc_netto * MNOZNIK_RABATU_LATO\n\n        wartosc_netto = round(wartosc_netto, PRECYZJA_KWOT)\n        stawka_vat = pozycja['vat']\n\n        if stawka_vat in MNOZNIKI_VAT:\n            kwota_vat = round(wartosc_netto * MNOZNIKI_VAT[stawka_vat], PRECYZJA_KWOT)\n        elif stawka_vat == STAWKA_VAT_ZERO:\n            kwota_vat = 0\n        else:\n            raise ValueError(BLAD_STAWKI)\n\n        suma_netto = suma_netto + wartosc_netto\n        suma_vat = suma_vat + kwota_vat\n\n    suma_brutto = suma_netto + suma_vat\n\n    if kod == KOD_STALY and suma_brutto > PROG_RABATU_STALY:\n        suma_brutto = suma_brutto - KWOTA_RABATU_STALY\n\n    if kod is not None and kod not in DOZWOLONE_KODY:\n        raise ValueError(BLAD_KODU)\n\n    return {\n        'netto': round(suma_netto, PRECYZJA_KWOT),\n        'vat': round(suma_vat, PRECYZJA_KWOT),\n        'brutto': round(suma_brutto, PRECYZJA_KWOT),\n    }\n```",
   "czas_s": 10.78,
   "usage": {
    "prompt_tokens": 518,
    "completion_tokens": 684,
    "total_tokens": 1202,
    "cost": 0.007876,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.007876,
     "upstream_inference_prompt_cost": 0.001036,
     "upstream_inference_completions_cost": 0.00684
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.007876,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.007876,
   "ocena": {
    "zaliczone": 16,
    "testy": 16,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "KOD_LATO = 'LATO10'\nKOD_STALY = 'STALY'\nDOZWOLONE_KODY = (KOD_LATO, KOD_STALY)\n\nVAT_PODSTAWOWY = 23\nMNOZNIK_RABATU_LATO = 0.9\n\nPROG_RABATU_STALY = 100\nKWOTA_RABATU_STALY = 5\n\nPRECYZJA_KWOT = 2\n\nMNOZNIKI_VAT = {\n    23: 0.23,\n    8: 0.08,\n    5: 0.05,\n}\nSTAWKA_VAT_ZERO = 0\n\nBLAD_STAWKI = 'zla stawka'\nBLAD_KODU = 'zly kod'\n\n\ndef oblicz_fakture(pozycje, kod=None):\n    # pozycje: lista słowników {'nazwa': str, 'netto': float, 'ilosc': int, 'vat': int}\n    suma_netto = 0\n    suma_vat = 0\n    suma_brutto = 0\n\n    for pozycja in pozycje:\n        if pozycja['ilosc'] <= 0:\n            continue\n\n        wartosc_netto = pozycja['netto'] * pozycja['ilosc']\n\n        if kod == KOD_LATO:\n            if pozycja['vat'] == VAT_PODSTAWOWY:\n                wartosc_netto = wartosc_netto * MNOZNIK_RABATU_LATO\n\n        wartosc_netto = round(wartosc_netto, PRECYZJA_KWOT)\n        stawka_vat = pozycja['vat']\n\n        if stawka_vat in MNOZNIKI_VAT:\n            kwota_vat = round(wartosc_netto * MNOZNIKI_VAT[stawka_vat], PRECYZJA_KWOT)\n        elif stawka_vat == STAWKA_VAT_ZERO:\n            kwota_vat = 0\n        else:\n            raise ValueError(BLAD_STAWKI)\n\n        suma_netto = suma_netto + wartosc_netto\n        suma_vat = suma_vat + kwota_vat\n\n    suma_brutto = suma_netto + suma_vat\n\n    if kod == KOD_STALY and suma_brutto > PROG_RABATU_STALY:\n        suma_brutto = suma_brutto - KWOTA_RABATU_STALY\n\n    if kod is not None and kod not in DOZWOLONE_KODY:\n        raise ValueError(BLAD_KODU)\n\n    return {\n        'netto': round(suma_netto, PRECYZJA_KWOT),\n        'vat': round(suma_vat, PRECYZJA_KWOT),\n        'brutto': round(suma_brutto, PRECYZJA_KWOT),\n    }\n",
   "tokeny": {
    "wej": 518,
    "wyj": 684,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K3_bugfix",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Poniższa klasa miała być cache'em LRU, ale ma błędy. Popraw ją.\n\nWymagania:\n- `LRUCache(capacity)` - capacity to liczba całkowita >= 0.\n- `get(key)` zwraca wartość albo -1, gdy klucza nie ma. Udany `get` czyni klucz „najświeżej użytym”.\n- `put(key, value)` wstawia albo aktualizuje wartość; klucz staje się najświeżej użyty. Gdy po wstawieniu NOWEGO klucza liczba elementów przekroczyłaby capacity, najpierw usuwany jest najdawniej użyty klucz. Aktualizacja istniejącego klucza nikogo nie usuwa.\n- Przy capacity == 0 nic nie jest przechowywane (put nic nie robi, get zawsze -1), bez wyjątków.\n- get i put mają działać w średnim czasie O(1).\n- Zachowaj nazwę klasy i metod.\n\n```python\nclass LRUCache:\n    def __init__(self, capacity):\n        self.capacity = capacity\n        self.data = {}\n        self.order = []\n\n    def get(self, key):\n        if key in self.data:\n            return self.data[key]\n        return -1\n\n    def put(self, key, value):\n        if len(self.data) >= self.capacity:\n            oldest = self.order.pop()\n            del self.data[oldest]\n        self.data[key] = value\n        self.order.append(key)\n```\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nfrom collections import OrderedDict\n\n\nclass LRUCache:\n    def __init__(self, capacity):\n        self.capacity = capacity\n        self.data = OrderedDict()\n\n    def get(self, key):\n        if key not in self.data:\n            return -1\n        self.data.move_to_end(key)\n        return self.data[key]\n\n    def put(self, key, value):\n        if self.capacity == 0:\n            return\n\n        if key in self.data:\n            self.data[key] = value\n            self.data.move_to_end(key)\n            return\n\n        if len(self.data) >= self.capacity:\n            self.data.popitem(last=False)\n\n        self.data[key] = value\n```",
   "czas_s": 5.0,
   "usage": {
    "prompt_tokens": 382,
    "completion_tokens": 194,
    "total_tokens": 576,
    "cost": 0.002704,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002704,
     "upstream_inference_prompt_cost": 0.000764,
     "upstream_inference_completions_cost": 0.00194
    },
    "completion_tokens_details": {
     "reasoning_tokens": 45,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002704,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002704,
   "ocena": {
    "zaliczone": 12,
    "testy": 12,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "from collections import OrderedDict\n\n\nclass LRUCache:\n    def __init__(self, capacity):\n        self.capacity = capacity\n        self.data = OrderedDict()\n\n    def get(self, key):\n        if key not in self.data:\n            return -1\n        self.data.move_to_end(key)\n        return self.data[key]\n\n    def put(self, key, value):\n        if self.capacity == 0:\n            return\n\n        if key in self.data:\n            self.data[key] = value\n            self.data.move_to_end(key)\n            return\n\n        if len(self.data) >= self.capacity:\n            self.data.popitem(last=False)\n\n        self.data[key] = value\n",
   "tokeny": {
    "wej": 382,
    "wyj": 194,
    "rozumowanie": 45
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K5_regex",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Napisz funkcję Pythona `find_dates(text: str) -> list[str]`, która znajduje w tekście daty i zwraca je jako napisy ISO 'YYYY-MM-DD' w kolejności wystąpienia (duplikaty zostają).\n\nObsługiwane formaty:\n1. DD.MM.YYYY oraz D.M.YYYY (dzień i miesiąc 1-2 cyfry, separator kropka, rok 4 cyfry), np. 12.11.2026, 1.2.2027.\n2. YYYY-MM-DD (miesiąc i dzień dokładnie 2 cyfry), np. 2027-03-15.\n3. Zapis słowny: dzień (1-2 cyfry), spacja, nazwa miesiąca w dopełniaczu (stycznia, lutego, marca, kwietnia, maja, czerwca, lipca, sierpnia, września, października, listopada, grudnia; pierwsza litera może być wielka), spacja, rok 4 cyfry, np. \"9 listopada 2026\" albo \"3 Maja 2027\". Po roku może stać „r.” lub „r”.\n\nZasady:\n- Zwracaj tylko istniejące daty kalendarzowe (uwzględnij lata przestępne): 31.02.2026 i 2026-13-01 pomijasz.\n- Data nie może być częścią dłuższej liczby: znak tuż przed dopasowaniem i tuż po nim nie może być cyfrą.\n- Użyj wyrażeń regularnych (moduł re).\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nfrom datetime import date\nimport re\n\n\ndef find_dates(text: str) -> list[str]:\n    months = {\n        \"stycznia\": 1,\n        \"lutego\": 2,\n        \"marca\": 3,\n        \"kwietnia\": 4,\n        \"maja\": 5,\n        \"czerwca\": 6,\n        \"lipca\": 7,\n        \"sierpnia\": 8,\n        \"września\": 9,\n        \"października\": 10,\n        \"listopada\": 11,\n        \"grudnia\": 12,\n    }\n\n    months_pattern = (\n        r\"(?:[sS]tycznia|[lL]utego|[mM]arca|[kK]wietnia|[mM]aja|[cC]zerwca|\"\n        r\"[lL]ipca|[sS]ierpnia|[wW]rześnia|[pP]aździernika|[lL]istopada|[gG]rudnia)\"\n    )\n\n    pattern = re.compile(\n        rf\"(?<!\\d)(?:\"\n        rf\"(?P<d1>\\d{{1,2}})\\.(?P<m1>\\d{{1,2}})\\.(?P<y1>\\d{{4}})|\"\n        rf\"(?P<y2>\\d{{4}})-(?P<m2>\\d{{2}})-(?P<d2>\\d{{2}})|\"\n        rf\"(?P<d3>\\d{{1,2}}) (?P<m3>{months_pattern}) (?P<y3>\\d{{4}})(?: ?r\\.?)?\"\n        rf\")(?!\\d)\"\n    )\n\n    results = []\n    for match in pattern.finditer(text):\n        if match.group(\"d1\") is not None:\n            day = int(match.group(\"d1\"))\n            month = int(match.group(\"m1\"))\n            year = int(match.group(\"y1\"))\n        elif match.group(\"y2\") is not None:\n            year = int(match.group(\"y2\"))\n            month = int(match.group(\"m2\"))\n            day = int(match.group(\"d2\"))\n        else:\n            day = int(match.group(\"d3\"))\n            month = months[match.group(\"m3\").lower()]\n            year = int(match.group(\"y3\"))\n\n        try:\n            valid_date = date(year, month, day)\n            results.append(f\"{valid_date.year:04d}-{valid_date.month:02d}-{valid_date.day:02d}\")\n        except ValueError:\n            continue\n\n    return results\n```",
   "czas_s": 10.78,
   "usage": {
    "prompt_tokens": 440,
    "completion_tokens": 643,
    "total_tokens": 1083,
    "cost": 0.00731,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.00731,
     "upstream_inference_prompt_cost": 0.00088,
     "upstream_inference_completions_cost": 0.00643
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.00731,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.00731,
   "ocena": {
    "zaliczone": 13,
    "testy": 13,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "from datetime import date\nimport re\n\n\ndef find_dates(text: str) -> list[str]:\n    months = {\n        \"stycznia\": 1,\n        \"lutego\": 2,\n        \"marca\": 3,\n        \"kwietnia\": 4,\n        \"maja\": 5,\n        \"czerwca\": 6,\n        \"lipca\": 7,\n        \"sierpnia\": 8,\n        \"września\": 9,\n        \"października\": 10,\n        \"listopada\": 11,\n        \"grudnia\": 12,\n    }\n\n    months_pattern = (\n        r\"(?:[sS]tycznia|[lL]utego|[mM]arca|[kK]wietnia|[mM]aja|[cC]zerwca|\"\n        r\"[lL]ipca|[sS]ierpnia|[wW]rześnia|[pP]aździernika|[lL]istopada|[gG]rudnia)\"\n    )\n\n    pattern = re.compile(\n        rf\"(?<!\\d)(?:\"\n        rf\"(?P<d1>\\d{{1,2}})\\.(?P<m1>\\d{{1,2}})\\.(?P<y1>\\d{{4}})|\"\n        rf\"(?P<y2>\\d{{4}})-(?P<m2>\\d{{2}})-(?P<d2>\\d{{2}})|\"\n        rf\"(?P<d3>\\d{{1,2}}) (?P<m3>{months_pattern}) (?P<y3>\\d{{4}})(?: ?r\\.?)?\"\n        rf\")(?!\\d)\"\n    )\n\n    results = []\n    for match in pattern.finditer(text):\n        if match.group(\"d1\") is not None:\n            day = int(match.group(\"d1\"))\n            month = int(match.group(\"m1\"))\n            year = int(match.group(\"y1\"))\n        elif match.group(\"y2\") is not None:\n            year = int(match.group(\"y2\"))\n            month = int(match.group(\"m2\"))\n            day = int(match.group(\"d2\"))\n        else:\n            day = int(match.group(\"d3\"))\n            month = months[match.group(\"m3\").lower()]\n            year = int(match.group(\"y3\"))\n\n        try:\n            valid_date = date(year, month, day)\n            results.append(f\"{valid_date.year:04d}-{valid_date.month:02d}-{valid_date.day:02d}\")\n        except ValueError:\n            continue\n\n    return results\n",
   "tokeny": {
    "wej": 440,
    "wyj": 643,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K6_sql",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Baza SQLite ma schemat:\n\n```sql\nCREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT NOT NULL, city TEXT);\nCREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER NOT NULL REFERENCES customers(id),\n                     order_date TEXT NOT NULL,  -- 'YYYY-MM-DD'\n                     status TEXT NOT NULL,      -- np. 'paid', 'cancelled', 'pending'\n                     discount REAL);            -- rabat kwotowy na całe zamówienie, może być NULL\nCREATE TABLE order_items (id INTEGER PRIMARY KEY, order_id INTEGER NOT NULL REFERENCES orders(id),\n                          product TEXT NOT NULL, quantity INTEGER NOT NULL, unit_price REAL NOT NULL);\n```\n\nNapisz JEDNO zapytanie SELECT, które dla każdego klienta liczy jego zamówienia ze statusem dokładnie 'paid' złożone w roku 2026 (od 2026-01-01 do 2026-12-31 włącznie).\n- Wartość zamówienia = suma quantity*unit_price jego pozycji minus discount (NULL traktuj jak 0). Zamówienie bez żadnych pozycji ma sumę pozycji 0, ale też się liczy (i też odejmujesz rabat).\n- Pokaż tylko klientów, którzy mają co najmniej 2 takie zamówienia.\n- Kolumny w tej kolejności: name, city (gdy NULL, pokaż 'brak'), orders_count, total (suma wartości zamówień zaokrąglona funkcją ROUND do 2 miejsc).\n- Sortowanie: total malejąco, potem name rosnąco.\n\nZwróć wyłącznie zapytanie w jednym bloku ```sql```.",
   "odpowiedz": "```sql\nWITH order_totals AS (\n    SELECT \n        o.id AS order_id,\n        o.customer_id,\n        COALESCE(SUM(oi.quantity * oi.unit_price), 0) - COALESCE(o.discount, 0) AS order_value\n    FROM orders o\n    LEFT JOIN order_items oi ON o.id = oi.order_id\n    WHERE o.status = 'paid'\n      AND o.order_date >= '2026-01-01'\n      AND o.order_date <= '2026-12-31'\n    GROUP BY o.id, o.customer_id, o.discount\n)\nSELECT \n    c.name,\n    COALESCE(c.city, 'brak') AS city,\n    COUNT(ot.order_id) AS orders_count,\n    ROUND(SUM(ot.order_value), 2) AS total\nFROM customers c\nJOIN order_totals ot ON c.id = ot.customer_id\nGROUP BY c.id, c.name, c.city\nHAVING COUNT(ot.order_id) >= 2\nORDER BY total DESC, c.name ASC;\n```",
   "czas_s": 6.07,
   "usage": {
    "prompt_tokens": 391,
    "completion_tokens": 279,
    "total_tokens": 670,
    "cost": 0.003572,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.003572,
     "upstream_inference_prompt_cost": 0.000782,
     "upstream_inference_completions_cost": 0.00279
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.003572,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.003572,
   "ocena": {
    "zaliczone": 5,
    "testy": 5,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "WITH order_totals AS (\n    SELECT \n        o.id AS order_id,\n        o.customer_id,\n        COALESCE(SUM(oi.quantity * oi.unit_price), 0) - COALESCE(o.discount, 0) AS order_value\n    FROM orders o\n    LEFT JOIN order_items oi ON o.id = oi.order_id\n    WHERE o.status = 'paid'\n      AND o.order_date >= '2026-01-01'\n      AND o.order_date <= '2026-12-31'\n    GROUP BY o.id, o.customer_id, o.discount\n)\nSELECT \n    c.name,\n    COALESCE(c.city, 'brak') AS city,\n    COUNT(ot.order_id) AS orders_count,\n    ROUND(SUM(ot.order_value), 2) AS total\nFROM customers c\nJOIN order_totals ot ON c.id = ot.customer_id\nGROUP BY c.id, c.name, c.city\nHAVING COUNT(ot.order_id) >= 2\nORDER BY total DESC, c.name ASC",
   "tokeny": {
    "wej": 391,
    "wyj": 279,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K4_refaktor",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Zrefaktoryzuj poniższą funkcję: czytelne nazwy, stałe zamiast magicznych liczb, bez powtórzeń.\nWarunek twardy: zachowanie ma pozostać IDENTYCZNE dla każdego wejścia - te same wyniki co do bitu (floaty), te same wyjątki w tych samych sytuacjach i w tej samej kolejności sprawdzania. Nazwa funkcji i parametrów bez zmian.\n\n```python\ndef oblicz_fakture(pozycje, kod=None):\n    # pozycje: lista słowników {'nazwa': str, 'netto': float, 'ilosc': int, 'vat': int}\n    s=0;v=0;b=0\n    for p in pozycje:\n        if p['ilosc']<=0:\n            continue\n        n=p['netto']*p['ilosc']\n        if kod=='LATO10':\n            if p['vat']==23:\n                n=n*0.9\n        n=round(n,2)\n        if p['vat']==23:\n            x=round(n*0.23,2)\n        elif p['vat']==8:\n            x=round(n*0.08,2)\n        elif p['vat']==5:\n            x=round(n*0.05,2)\n        elif p['vat']==0:\n            x=0\n        else:\n            raise ValueError('zla stawka')\n        s=s+n\n        v=v+x\n    b=s+v\n    if kod=='STALY' and b>100:\n        b=b-5\n        s=s  # netto bez zmian\n    if kod is not None and kod not in ('LATO10','STALY'):\n        raise ValueError('zly kod')\n    return {'netto':round(s,2),'vat':round(v,2),'brutto':round(b,2)}\n```\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nKOD_RABAT_LATO = 'LATO10'\nKOD_RABAT_STALY = 'STALY'\nOBSLUGIWANE_KODY = (KOD_RABAT_LATO, KOD_RABAT_STALY)\n\nSTAWKA_RABATOWANA_LATO = 23\nMNOZNIK_RABATU_LATO = 0.9\n\nPROG_RABATU_STALEGO = 100\nKWOTA_RABATU_STALEGO = 5\n\nPRECYZJA_KWOT = 2\n\nSTAWKA_ZEROWA = 0\nSTAWKI_VAT = (\n    (23, 0.23),\n    (8, 0.08),\n    (5, 0.05),\n)\n\n\ndef _netto_pozycji(pozycja, kod):\n    netto = pozycja['netto'] * pozycja['ilosc']\n    if kod == KOD_RABAT_LATO:\n        if pozycja['vat'] == STAWKA_RABATOWANA_LATO:\n            netto = netto * MNOZNIK_RABATU_LATO\n    return round(netto, PRECYZJA_KWOT)\n\n\ndef _vat_pozycji(netto, stawka):\n    for wartosc_stawki, mnoznik in STAWKI_VAT:\n        if stawka == wartosc_stawki:\n            return round(netto * mnoznik, PRECYZJA_KWOT)\n    if stawka == STAWKA_ZEROWA:\n        return 0\n    raise ValueError('zla stawka')\n\n\ndef oblicz_fakture(pozycje, kod=None):\n    # pozycje: lista słowników {'nazwa': str, 'netto': float, 'ilosc': int, 'vat': int}\n    suma_netto = 0\n    suma_vat = 0\n\n    for pozycja in pozycje:\n        if pozycja['ilosc'] <= 0:\n            continue\n        netto = _netto_pozycji(pozycja, kod)\n        vat = _vat_pozycji(netto, pozycja['vat'])\n        suma_netto = suma_netto + netto\n        suma_vat = suma_vat + vat\n\n    suma_brutto = suma_netto + suma_vat\n    if kod == KOD_RABAT_STALY and suma_brutto > PROG_RABATU_STALEGO:\n        suma_brutto = suma_brutto - KWOTA_RABATU_STALEGO  # netto bez zmian\n\n    if kod is not None and kod not in OBSLUGIWANE_KODY:\n        raise ValueError('zly kod')\n\n    return {\n        'netto': round(suma_netto, PRECYZJA_KWOT),\n        'vat': round(suma_vat, PRECYZJA_KWOT),\n        'brutto': round(suma_brutto, PRECYZJA_KWOT),\n    }\n```",
   "czas_s": 24.84,
   "usage": {
    "prompt_tokens": 665,
    "completion_tokens": 2533,
    "total_tokens": 3198,
    "cost": 0.05332,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.05332,
     "upstream_inference_prompt_cost": 0.00266,
     "upstream_inference_completions_cost": 0.05066
    },
    "completion_tokens_details": {
     "reasoning_tokens": 1582,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.05332,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.05332,
   "ocena": {
    "zaliczone": 16,
    "testy": 16,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "KOD_RABAT_LATO = 'LATO10'\nKOD_RABAT_STALY = 'STALY'\nOBSLUGIWANE_KODY = (KOD_RABAT_LATO, KOD_RABAT_STALY)\n\nSTAWKA_RABATOWANA_LATO = 23\nMNOZNIK_RABATU_LATO = 0.9\n\nPROG_RABATU_STALEGO = 100\nKWOTA_RABATU_STALEGO = 5\n\nPRECYZJA_KWOT = 2\n\nSTAWKA_ZEROWA = 0\nSTAWKI_VAT = (\n    (23, 0.23),\n    (8, 0.08),\n    (5, 0.05),\n)\n\n\ndef _netto_pozycji(pozycja, kod):\n    netto = pozycja['netto'] * pozycja['ilosc']\n    if kod == KOD_RABAT_LATO:\n        if pozycja['vat'] == STAWKA_RABATOWANA_LATO:\n            netto = netto * MNOZNIK_RABATU_LATO\n    return round(netto, PRECYZJA_KWOT)\n\n\ndef _vat_pozycji(netto, stawka):\n    for wartosc_stawki, mnoznik in STAWKI_VAT:\n        if stawka == wartosc_stawki:\n            return round(netto * mnoznik, PRECYZJA_KWOT)\n    if stawka == STAWKA_ZEROWA:\n        return 0\n    raise ValueError('zla stawka')\n\n\ndef oblicz_fakture(pozycje, kod=None):\n    # pozycje: lista słowników {'nazwa': str, 'netto': float, 'ilosc': int, 'vat': int}\n    suma_netto = 0\n    suma_vat = 0\n\n    for pozycja in pozycje:\n        if pozycja['ilosc'] <= 0:\n            continue\n        netto = _netto_pozycji(pozycja, kod)\n        vat = _vat_pozycji(netto, pozycja['vat'])\n        suma_netto = suma_netto + netto\n        suma_vat = suma_vat + vat\n\n    suma_brutto = suma_netto + suma_vat\n    if kod == KOD_RABAT_STALY and suma_brutto > PROG_RABATU_STALEGO:\n        suma_brutto = suma_brutto - KWOTA_RABATU_STALEGO  # netto bez zmian\n\n    if kod is not None and kod not in OBSLUGIWANE_KODY:\n        raise ValueError('zly kod')\n\n    return {\n        'netto': round(suma_netto, PRECYZJA_KWOT),\n        'vat': round(suma_vat, PRECYZJA_KWOT),\n        'brutto': round(suma_brutto, PRECYZJA_KWOT),\n    }\n",
   "tokeny": {
    "wej": 665,
    "wyj": 2533,
    "rozumowanie": 1582
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K4_refaktor",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Zrefaktoryzuj poniższą funkcję: czytelne nazwy, stałe zamiast magicznych liczb, bez powtórzeń.\nWarunek twardy: zachowanie ma pozostać IDENTYCZNE dla każdego wejścia - te same wyniki co do bitu (floaty), te same wyjątki w tych samych sytuacjach i w tej samej kolejności sprawdzania. Nazwa funkcji i parametrów bez zmian.\n\n```python\ndef oblicz_fakture(pozycje, kod=None):\n    # pozycje: lista słowników {'nazwa': str, 'netto': float, 'ilosc': int, 'vat': int}\n    s=0;v=0;b=0\n    for p in pozycje:\n        if p['ilosc']<=0:\n            continue\n        n=p['netto']*p['ilosc']\n        if kod=='LATO10':\n            if p['vat']==23:\n                n=n*0.9\n        n=round(n,2)\n        if p['vat']==23:\n            x=round(n*0.23,2)\n        elif p['vat']==8:\n            x=round(n*0.08,2)\n        elif p['vat']==5:\n            x=round(n*0.05,2)\n        elif p['vat']==0:\n            x=0\n        else:\n            raise ValueError('zla stawka')\n        s=s+n\n        v=v+x\n    b=s+v\n    if kod=='STALY' and b>100:\n        b=b-5\n        s=s  # netto bez zmian\n    if kod is not None and kod not in ('LATO10','STALY'):\n        raise ValueError('zly kod')\n    return {'netto':round(s,2),'vat':round(v,2),'brutto':round(b,2)}\n```\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\ndef oblicz_fakture(pozycje, kod=None):\n    MIEJSCA_PO_PRZECINKU = 2\n\n    VAT_STANDARDOWY = 23\n    VAT_OBNIZONY = 8\n    VAT_MINIMALNY = 5\n    VAT_ZEROWY = 0\n\n    MNOZNIK_VAT_STANDARDOWY = 0.23\n    MNOZNIK_VAT_OBNIZONY = 0.08\n    MNOZNIK_VAT_MINIMALNY = 0.05\n\n    KOD_RABATU_LETNIEGO = 'LATO10'\n    KOD_RABATU_STALEGO = 'STALY'\n    DOZWOLONE_KODY = ('LATO10', 'STALY')\n    MNOZNIK_RABATU_LETNIEGO = 0.9\n    PROG_RABATU_STALEGO = 100\n    KWOTA_RABATU_STALEGO = 5\n\n    suma_netto = 0\n    suma_vat = 0\n\n    for pozycja in pozycje:\n        if pozycja['ilosc'] <= 0:\n            continue\n\n        kwota_netto = pozycja['netto'] * pozycja['ilosc']\n\n        if kod == KOD_RABATU_LETNIEGO:\n            if pozycja['vat'] == VAT_STANDARDOWY:\n                kwota_netto = kwota_netto * MNOZNIK_RABATU_LETNIEGO\n\n        kwota_netto = round(kwota_netto, MIEJSCA_PO_PRZECINKU)\n\n        # Zachowujemy kolejność porównań i osobne odczyty klucza 'vat'.\n        if pozycja['vat'] == VAT_STANDARDOWY:\n            mnoznik_vat = MNOZNIK_VAT_STANDARDOWY\n        elif pozycja['vat'] == VAT_OBNIZONY:\n            mnoznik_vat = MNOZNIK_VAT_OBNIZONY\n        elif pozycja['vat'] == VAT_MINIMALNY:\n            mnoznik_vat = MNOZNIK_VAT_MINIMALNY\n        elif pozycja['vat'] == VAT_ZEROWY:\n            mnoznik_vat = None\n        else:\n            raise ValueError('zla stawka')\n\n        # Dla VAT 0 zachowujemy całkowite zero, bez mnożenia i round().\n        if mnoznik_vat is None:\n            kwota_vat = 0\n        else:\n            kwota_vat = round(\n                kwota_netto * mnoznik_vat,\n                MIEJSCA_PO_PRZECINKU,\n            )\n\n        suma_netto = suma_netto + kwota_netto\n        suma_vat = suma_vat + kwota_vat\n\n    suma_brutto = suma_netto + suma_vat\n\n    if kod == KOD_RABATU_STALEGO and suma_brutto > PROG_RABATU_STALEGO:\n        suma_brutto = suma_brutto - KWOTA_RABATU_STALEGO\n\n    if kod is not None and kod not in DOZWOLONE_KODY:\n        raise ValueError('zly kod')\n\n    return {\n        'netto': round(suma_netto, MIEJSCA_PO_PRZECINKU),\n        'vat': round(suma_vat, MIEJSCA_PO_PRZECINKU),\n        'brutto': round(suma_brutto, MIEJSCA_PO_PRZECINKU),\n    }\n```",
   "czas_s": 20.56,
   "usage": {
    "prompt_tokens": 467,
    "completion_tokens": 1237,
    "total_tokens": 1704,
    "cost": 0.013304,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.013304,
     "upstream_inference_prompt_cost": 0.000934,
     "upstream_inference_completions_cost": 0.01237
    },
    "completion_tokens_details": {
     "reasoning_tokens": 454,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.013304,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.013304,
   "ocena": {
    "zaliczone": 16,
    "testy": 16,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "def oblicz_fakture(pozycje, kod=None):\n    MIEJSCA_PO_PRZECINKU = 2\n\n    VAT_STANDARDOWY = 23\n    VAT_OBNIZONY = 8\n    VAT_MINIMALNY = 5\n    VAT_ZEROWY = 0\n\n    MNOZNIK_VAT_STANDARDOWY = 0.23\n    MNOZNIK_VAT_OBNIZONY = 0.08\n    MNOZNIK_VAT_MINIMALNY = 0.05\n\n    KOD_RABATU_LETNIEGO = 'LATO10'\n    KOD_RABATU_STALEGO = 'STALY'\n    DOZWOLONE_KODY = ('LATO10', 'STALY')\n    MNOZNIK_RABATU_LETNIEGO = 0.9\n    PROG_RABATU_STALEGO = 100\n    KWOTA_RABATU_STALEGO = 5\n\n    suma_netto = 0\n    suma_vat = 0\n\n    for pozycja in pozycje:\n        if pozycja['ilosc'] <= 0:\n            continue\n\n        kwota_netto = pozycja['netto'] * pozycja['ilosc']\n\n        if kod == KOD_RABATU_LETNIEGO:\n            if pozycja['vat'] == VAT_STANDARDOWY:\n                kwota_netto = kwota_netto * MNOZNIK_RABATU_LETNIEGO\n\n        kwota_netto = round(kwota_netto, MIEJSCA_PO_PRZECINKU)\n\n        # Zachowujemy kolejność porównań i osobne odczyty klucza 'vat'.\n        if pozycja['vat'] == VAT_STANDARDOWY:\n            mnoznik_vat = MNOZNIK_VAT_STANDARDOWY\n        elif pozycja['vat'] == VAT_OBNIZONY:\n            mnoznik_vat = MNOZNIK_VAT_OBNIZONY\n        elif pozycja['vat'] == VAT_MINIMALNY:\n            mnoznik_vat = MNOZNIK_VAT_MINIMALNY\n        elif pozycja['vat'] == VAT_ZEROWY:\n            mnoznik_vat = None\n        else:\n            raise ValueError('zla stawka')\n\n        # Dla VAT 0 zachowujemy całkowite zero, bez mnożenia i round().\n        if mnoznik_vat is None:\n            kwota_vat = 0\n        else:\n            kwota_vat = round(\n                kwota_netto * mnoznik_vat,\n                MIEJSCA_PO_PRZECINKU,\n            )\n\n        suma_netto = suma_netto + kwota_netto\n        suma_vat = suma_vat + kwota_vat\n\n    suma_brutto = suma_netto + suma_vat\n\n    if kod == KOD_RABATU_STALEGO and suma_brutto > PROG_RABATU_STALEGO:\n        suma_brutto = suma_brutto - KWOTA_RABATU_STALEGO\n\n    if kod is not None and kod not in DOZWOLONE_KODY:\n        raise ValueError('zly kod')\n\n    return {\n        'netto': round(suma_netto, MIEJSCA_PO_PRZECINKU),\n        'vat': round(suma_vat, MIEJSCA_PO_PRZECINKU),\n        'brutto': round(suma_brutto, MIEJSCA_PO_PRZECINKU),\n    }\n",
   "tokeny": {
    "wej": 467,
    "wyj": 1237,
    "rozumowanie": 454
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K5_regex",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Napisz funkcję Pythona `find_dates(text: str) -> list[str]`, która znajduje w tekście daty i zwraca je jako napisy ISO 'YYYY-MM-DD' w kolejności wystąpienia (duplikaty zostają).\n\nObsługiwane formaty:\n1. DD.MM.YYYY oraz D.M.YYYY (dzień i miesiąc 1-2 cyfry, separator kropka, rok 4 cyfry), np. 12.11.2026, 1.2.2027.\n2. YYYY-MM-DD (miesiąc i dzień dokładnie 2 cyfry), np. 2027-03-15.\n3. Zapis słowny: dzień (1-2 cyfry), spacja, nazwa miesiąca w dopełniaczu (stycznia, lutego, marca, kwietnia, maja, czerwca, lipca, sierpnia, września, października, listopada, grudnia; pierwsza litera może być wielka), spacja, rok 4 cyfry, np. \"9 listopada 2026\" albo \"3 Maja 2027\". Po roku może stać „r.” lub „r”.\n\nZasady:\n- Zwracaj tylko istniejące daty kalendarzowe (uwzględnij lata przestępne): 31.02.2026 i 2026-13-01 pomijasz.\n- Data nie może być częścią dłuższej liczby: znak tuż przed dopasowaniem i tuż po nim nie może być cyfrą.\n- Użyj wyrażeń regularnych (moduł re).\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nimport re\nfrom datetime import date\n\n_MONTHS = [\n    \"stycznia\", \"lutego\", \"marca\", \"kwietnia\", \"maja\", \"czerwca\",\n    \"lipca\", \"sierpnia\", \"września\", \"października\", \"listopada\", \"grudnia\",\n]\n\n_MONTH_NUM = {name: i + 1 for i, name in enumerate(_MONTHS)}\n\n_MONTH_ALT = \"|\".join(\n    \"[\" + m[0].upper() + m[0] + \"]\" + re.escape(m[1:]) for m in _MONTHS\n)\n\n_DATE_RE = re.compile(\n    r\"(?<![0-9])\"\n    r\"(?:\"\n    r\"(?P<d1>[0-9]{1,2})\\.(?P<m1>[0-9]{1,2})\\.(?P<y1>[0-9]{4})\"\n    r\"|\"\n    r\"(?P<y2>[0-9]{4})-(?P<m2>[0-9]{2})-(?P<d2>[0-9]{2})\"\n    r\"|\"\n    r\"(?P<d3>[0-9]{1,2}) (?P<mn>\" + _MONTH_ALT + r\") (?P<y3>[0-9]{4})\"\n    r\")\"\n    r\"(?![0-9])\"\n    r\"(?:\\s?r\\.?)?\"\n)\n\n\ndef find_dates(text: str) -> list[str]:\n    results: list[str] = []\n    for m in _DATE_RE.finditer(text):\n        if m.group(\"y1\") is not None:\n            y, mo, d = int(m.group(\"y1\")), int(m.group(\"m1\")), int(m.group(\"d1\"))\n        elif m.group(\"y2\") is not None:\n            y, mo, d = int(m.group(\"y2\")), int(m.group(\"m2\")), int(m.group(\"d2\"))\n        else:\n            y = int(m.group(\"y3\"))\n            mo = _MONTH_NUM[m.group(\"mn\").lower()]\n            d = int(m.group(\"d3\"))\n        try:\n            dt = date(y, mo, d)\n        except ValueError:\n            continue\n        results.append(f\"{dt.year:04d}-{dt.month:02d}-{dt.day:02d}\")\n    return results\n```",
   "czas_s": 14.69,
   "usage": {
    "prompt_tokens": 613,
    "completion_tokens": 1513,
    "total_tokens": 2126,
    "cost": 0.032712,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.032712,
     "upstream_inference_prompt_cost": 0.002452,
     "upstream_inference_completions_cost": 0.03026
    },
    "completion_tokens_details": {
     "reasoning_tokens": 787,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.032712,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.032712,
   "ocena": {
    "zaliczone": 13,
    "testy": 13,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "import re\nfrom datetime import date\n\n_MONTHS = [\n    \"stycznia\", \"lutego\", \"marca\", \"kwietnia\", \"maja\", \"czerwca\",\n    \"lipca\", \"sierpnia\", \"września\", \"października\", \"listopada\", \"grudnia\",\n]\n\n_MONTH_NUM = {name: i + 1 for i, name in enumerate(_MONTHS)}\n\n_MONTH_ALT = \"|\".join(\n    \"[\" + m[0].upper() + m[0] + \"]\" + re.escape(m[1:]) for m in _MONTHS\n)\n\n_DATE_RE = re.compile(\n    r\"(?<![0-9])\"\n    r\"(?:\"\n    r\"(?P<d1>[0-9]{1,2})\\.(?P<m1>[0-9]{1,2})\\.(?P<y1>[0-9]{4})\"\n    r\"|\"\n    r\"(?P<y2>[0-9]{4})-(?P<m2>[0-9]{2})-(?P<d2>[0-9]{2})\"\n    r\"|\"\n    r\"(?P<d3>[0-9]{1,2}) (?P<mn>\" + _MONTH_ALT + r\") (?P<y3>[0-9]{4})\"\n    r\")\"\n    r\"(?![0-9])\"\n    r\"(?:\\s?r\\.?)?\"\n)\n\n\ndef find_dates(text: str) -> list[str]:\n    results: list[str] = []\n    for m in _DATE_RE.finditer(text):\n        if m.group(\"y1\") is not None:\n            y, mo, d = int(m.group(\"y1\")), int(m.group(\"m1\")), int(m.group(\"d1\"))\n        elif m.group(\"y2\") is not None:\n            y, mo, d = int(m.group(\"y2\")), int(m.group(\"m2\")), int(m.group(\"d2\"))\n        else:\n            y = int(m.group(\"y3\"))\n            mo = _MONTH_NUM[m.group(\"mn\").lower()]\n            d = int(m.group(\"d3\"))\n        try:\n            dt = date(y, mo, d)\n        except ValueError:\n            continue\n        results.append(f\"{dt.year:04d}-{dt.month:02d}-{dt.day:02d}\")\n    return results\n",
   "tokeny": {
    "wej": 613,
    "wyj": 1513,
    "rozumowanie": 787
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K5_regex",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Napisz funkcję Pythona `find_dates(text: str) -> list[str]`, która znajduje w tekście daty i zwraca je jako napisy ISO 'YYYY-MM-DD' w kolejności wystąpienia (duplikaty zostają).\n\nObsługiwane formaty:\n1. DD.MM.YYYY oraz D.M.YYYY (dzień i miesiąc 1-2 cyfry, separator kropka, rok 4 cyfry), np. 12.11.2026, 1.2.2027.\n2. YYYY-MM-DD (miesiąc i dzień dokładnie 2 cyfry), np. 2027-03-15.\n3. Zapis słowny: dzień (1-2 cyfry), spacja, nazwa miesiąca w dopełniaczu (stycznia, lutego, marca, kwietnia, maja, czerwca, lipca, sierpnia, września, października, listopada, grudnia; pierwsza litera może być wielka), spacja, rok 4 cyfry, np. \"9 listopada 2026\" albo \"3 Maja 2027\". Po roku może stać „r.” lub „r”.\n\nZasady:\n- Zwracaj tylko istniejące daty kalendarzowe (uwzględnij lata przestępne): 31.02.2026 i 2026-13-01 pomijasz.\n- Data nie może być częścią dłuższej liczby: znak tuż przed dopasowaniem i tuż po nim nie może być cyfrą.\n- Użyj wyrażeń regularnych (moduł re).\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nimport re\nfrom datetime import date\n\n_MONTHS = {\n    \"stycznia\": 1,\n    \"lutego\": 2,\n    \"marca\": 3,\n    \"kwietnia\": 4,\n    \"maja\": 5,\n    \"czerwca\": 6,\n    \"lipca\": 7,\n    \"sierpnia\": 8,\n    \"września\": 9,\n    \"października\": 10,\n    \"listopada\": 11,\n    \"grudnia\": 12,\n}\n\n_MONTH_PATTERN = \"|\".join(\n    f\"[{name[0]}{name[0].upper()}]{re.escape(name[1:])}\"\n    for name in _MONTHS\n)\n\n_DATE_PATTERN = re.compile(\n    r\"(?<!\\d)(?:\"\n    r\"(?P<dot_day>[0-9]{1,2})\\.\"\n    r\"(?P<dot_month>[0-9]{1,2})\\.\"\n    r\"(?P<dot_year>[0-9]{4})\"\n    r\"|\"\n    r\"(?P<iso_year>[0-9]{4})-\"\n    r\"(?P<iso_month>[0-9]{2})-\"\n    r\"(?P<iso_day>[0-9]{2})\"\n    r\"|\"\n    r\"(?P<word_day>[0-9]{1,2}) \"\n    rf\"(?P<word_month>{_MONTH_PATTERN}) \"\n    r\"(?P<word_year>[0-9]{4})\"\n    r\")(?!\\d)\"\n)\n\n\ndef find_dates(text: str) -> list[str]:\n    result: list[str] = []\n\n    for match in _DATE_PATTERN.finditer(text):\n        if match.group(\"dot_year\") is not None:\n            year = int(match.group(\"dot_year\"))\n            month = int(match.group(\"dot_month\"))\n            day = int(match.group(\"dot_day\"))\n        elif match.group(\"iso_year\") is not None:\n            year = int(match.group(\"iso_year\"))\n            month = int(match.group(\"iso_month\"))\n            day = int(match.group(\"iso_day\"))\n        else:\n            year = int(match.group(\"word_year\"))\n            month = _MONTHS[match.group(\"word_month\").lower()]\n            day = int(match.group(\"word_day\"))\n\n        try:\n            parsed_date = date(year, month, day)\n        except ValueError:\n            continue\n\n        result.append(parsed_date.isoformat())\n\n    return result\n```",
   "czas_s": 15.4,
   "usage": {
    "prompt_tokens": 415,
    "completion_tokens": 827,
    "total_tokens": 1242,
    "cost": 0.0091,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.0091,
     "upstream_inference_prompt_cost": 0.00083,
     "upstream_inference_completions_cost": 0.00827
    },
    "completion_tokens_details": {
     "reasoning_tokens": 288,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.0091,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.0091,
   "ocena": {
    "zaliczone": 13,
    "testy": 13,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "import re\nfrom datetime import date\n\n_MONTHS = {\n    \"stycznia\": 1,\n    \"lutego\": 2,\n    \"marca\": 3,\n    \"kwietnia\": 4,\n    \"maja\": 5,\n    \"czerwca\": 6,\n    \"lipca\": 7,\n    \"sierpnia\": 8,\n    \"września\": 9,\n    \"października\": 10,\n    \"listopada\": 11,\n    \"grudnia\": 12,\n}\n\n_MONTH_PATTERN = \"|\".join(\n    f\"[{name[0]}{name[0].upper()}]{re.escape(name[1:])}\"\n    for name in _MONTHS\n)\n\n_DATE_PATTERN = re.compile(\n    r\"(?<!\\d)(?:\"\n    r\"(?P<dot_day>[0-9]{1,2})\\.\"\n    r\"(?P<dot_month>[0-9]{1,2})\\.\"\n    r\"(?P<dot_year>[0-9]{4})\"\n    r\"|\"\n    r\"(?P<iso_year>[0-9]{4})-\"\n    r\"(?P<iso_month>[0-9]{2})-\"\n    r\"(?P<iso_day>[0-9]{2})\"\n    r\"|\"\n    r\"(?P<word_day>[0-9]{1,2}) \"\n    rf\"(?P<word_month>{_MONTH_PATTERN}) \"\n    r\"(?P<word_year>[0-9]{4})\"\n    r\")(?!\\d)\"\n)\n\n\ndef find_dates(text: str) -> list[str]:\n    result: list[str] = []\n\n    for match in _DATE_PATTERN.finditer(text):\n        if match.group(\"dot_year\") is not None:\n            year = int(match.group(\"dot_year\"))\n            month = int(match.group(\"dot_month\"))\n            day = int(match.group(\"dot_day\"))\n        elif match.group(\"iso_year\") is not None:\n            year = int(match.group(\"iso_year\"))\n            month = int(match.group(\"iso_month\"))\n            day = int(match.group(\"iso_day\"))\n        else:\n            year = int(match.group(\"word_year\"))\n            month = _MONTHS[match.group(\"word_month\").lower()]\n            day = int(match.group(\"word_day\"))\n\n        try:\n            parsed_date = date(year, month, day)\n        except ValueError:\n            continue\n\n        result.append(parsed_date.isoformat())\n\n    return result\n",
   "tokeny": {
    "wej": 415,
    "wyj": 827,
    "rozumowanie": 288
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K6_sql",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Baza SQLite ma schemat:\n\n```sql\nCREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT NOT NULL, city TEXT);\nCREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER NOT NULL REFERENCES customers(id),\n                     order_date TEXT NOT NULL,  -- 'YYYY-MM-DD'\n                     status TEXT NOT NULL,      -- np. 'paid', 'cancelled', 'pending'\n                     discount REAL);            -- rabat kwotowy na całe zamówienie, może być NULL\nCREATE TABLE order_items (id INTEGER PRIMARY KEY, order_id INTEGER NOT NULL REFERENCES orders(id),\n                          product TEXT NOT NULL, quantity INTEGER NOT NULL, unit_price REAL NOT NULL);\n```\n\nNapisz JEDNO zapytanie SELECT, które dla każdego klienta liczy jego zamówienia ze statusem dokładnie 'paid' złożone w roku 2026 (od 2026-01-01 do 2026-12-31 włącznie).\n- Wartość zamówienia = suma quantity*unit_price jego pozycji minus discount (NULL traktuj jak 0). Zamówienie bez żadnych pozycji ma sumę pozycji 0, ale też się liczy (i też odejmujesz rabat).\n- Pokaż tylko klientów, którzy mają co najmniej 2 takie zamówienia.\n- Kolumny w tej kolejności: name, city (gdy NULL, pokaż 'brak'), orders_count, total (suma wartości zamówień zaokrąglona funkcją ROUND do 2 miejsc).\n- Sortowanie: total malejąco, potem name rosnąco.\n\nZwróć wyłącznie zapytanie w jednym bloku ```sql```.",
   "odpowiedz": "```sql\nSELECT\n    c.name,\n    COALESCE(c.city, 'brak') AS city,\n    COUNT(*) AS orders_count,\n    ROUND(SUM(\n        COALESCE((\n            SELECT SUM(oi.quantity * oi.unit_price)\n            FROM order_items AS oi\n            WHERE oi.order_id = o.id\n        ), 0) - COALESCE(o.discount, 0)\n    ), 2) AS total\nFROM customers AS c\nJOIN orders AS o ON o.customer_id = c.id\nWHERE o.status = 'paid'\n  AND o.order_date BETWEEN '2026-01-01' AND '2026-12-31'\nGROUP BY c.id, c.name, c.city\nHAVING COUNT(*) >= 2\nORDER BY total DESC, c.name ASC;\n```",
   "czas_s": 2.78,
   "usage": {
    "prompt_tokens": 361,
    "completion_tokens": 171,
    "total_tokens": 532,
    "cost": 0.002432,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002432,
     "upstream_inference_prompt_cost": 0.000722,
     "upstream_inference_completions_cost": 0.00171
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002432,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002432,
   "ocena": {
    "zaliczone": 5,
    "testy": 5,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "SELECT\n    c.name,\n    COALESCE(c.city, 'brak') AS city,\n    COUNT(*) AS orders_count,\n    ROUND(SUM(\n        COALESCE((\n            SELECT SUM(oi.quantity * oi.unit_price)\n            FROM order_items AS oi\n            WHERE oi.order_id = o.id\n        ), 0) - COALESCE(o.discount, 0)\n    ), 2) AS total\nFROM customers AS c\nJOIN orders AS o ON o.customer_id = c.id\nWHERE o.status = 'paid'\n  AND o.order_date BETWEEN '2026-01-01' AND '2026-12-31'\nGROUP BY c.id, c.name, c.city\nHAVING COUNT(*) >= 2\nORDER BY total DESC, c.name ASC",
   "tokeny": {
    "wej": 361,
    "wyj": 171,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "kod",
   "zadanie": "K6_sql",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Baza SQLite ma schemat:\n\n```sql\nCREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT NOT NULL, city TEXT);\nCREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER NOT NULL REFERENCES customers(id),\n                     order_date TEXT NOT NULL,  -- 'YYYY-MM-DD'\n                     status TEXT NOT NULL,      -- np. 'paid', 'cancelled', 'pending'\n                     discount REAL);            -- rabat kwotowy na całe zamówienie, może być NULL\nCREATE TABLE order_items (id INTEGER PRIMARY KEY, order_id INTEGER NOT NULL REFERENCES orders(id),\n                          product TEXT NOT NULL, quantity INTEGER NOT NULL, unit_price REAL NOT NULL);\n```\n\nNapisz JEDNO zapytanie SELECT, które dla każdego klienta liczy jego zamówienia ze statusem dokładnie 'paid' złożone w roku 2026 (od 2026-01-01 do 2026-12-31 włącznie).\n- Wartość zamówienia = suma quantity*unit_price jego pozycji minus discount (NULL traktuj jak 0). Zamówienie bez żadnych pozycji ma sumę pozycji 0, ale też się liczy (i też odejmujesz rabat).\n- Pokaż tylko klientów, którzy mają co najmniej 2 takie zamówienia.\n- Kolumny w tej kolejności: name, city (gdy NULL, pokaż 'brak'), orders_count, total (suma wartości zamówień zaokrąglona funkcją ROUND do 2 miejsc).\n- Sortowanie: total malejąco, potem name rosnąco.\n\nZwróć wyłącznie zapytanie w jednym bloku ```sql```.",
   "odpowiedz": "```sql\nSELECT\n    c.name,\n    COALESCE(c.city, 'brak') AS city,\n    COUNT(o.id) AS orders_count,\n    ROUND(SUM(COALESCE(oi.items_total, 0) - COALESCE(o.discount, 0)), 2) AS total\nFROM customers AS c\nJOIN orders AS o\n    ON o.customer_id = c.id\nLEFT JOIN (\n    SELECT order_id, SUM(quantity * unit_price) AS items_total\n    FROM order_items\n    GROUP BY order_id\n) AS oi\n    ON oi.order_id = o.id\nWHERE o.status = 'paid'\n  AND o.order_date >= '2026-01-01'\n  AND o.order_date <= '2026-12-31'\nGROUP BY c.id, c.name, c.city\nHAVING COUNT(o.id) >= 2\nORDER BY total DESC, c.name ASC;\n```",
   "czas_s": 7.56,
   "usage": {
    "prompt_tokens": 675,
    "completion_tokens": 796,
    "total_tokens": 1471,
    "cost": 0.01862,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.01862,
     "upstream_inference_prompt_cost": 0.0027,
     "upstream_inference_completions_cost": 0.01592
    },
    "completion_tokens_details": {
     "reasoning_tokens": 473,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.01862,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.01862,
   "ocena": {
    "zaliczone": 5,
    "testy": 5,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "SELECT\n    c.name,\n    COALESCE(c.city, 'brak') AS city,\n    COUNT(o.id) AS orders_count,\n    ROUND(SUM(COALESCE(oi.items_total, 0) - COALESCE(o.discount, 0)), 2) AS total\nFROM customers AS c\nJOIN orders AS o\n    ON o.customer_id = c.id\nLEFT JOIN (\n    SELECT order_id, SUM(quantity * unit_price) AS items_total\n    FROM order_items\n    GROUP BY order_id\n) AS oi\n    ON oi.order_id = o.id\nWHERE o.status = 'paid'\n  AND o.order_date >= '2026-01-01'\n  AND o.order_date <= '2026-12-31'\nGROUP BY c.id, c.name, c.city\nHAVING COUNT(o.id) >= 2\nORDER BY total DESC, c.name ASC",
   "tokeny": {
    "wej": 675,
    "wyj": 796,
    "rozumowanie": 473
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R1_rycerze",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Na wyspie mieszkają rycerze (zawsze mówią prawdę) i kłamcy (zawsze kłamią). Spotykasz trzy osoby.\nA mówi: „B i C są tego samego typu”.\nB mówi: „A jest kłamcą”.\nC mówi: „Ja i A jesteśmy różnego typu”.\nKto z nich jest rycerzem? Podaj litery wszystkich rycerzy (albo „nikt”).\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Rozważmy dwa przypadki.\n\n**Przypadek 1: A jest rycerzem.**\nWtedy B i C są tego samego typu. B mówi „A jest kłamcą” – to fałsz, więc B jest kłamcą. Zatem C też jest kłamcą. C mówi „Ja i A jesteśmy różnego typu” – C kłamca, A rycerz, więc to zdanie jest prawdziwe, a kłamca nie może powiedzieć prawdy. Sprzeczność.\n\n**Przypadek 2: A jest kłamcą.**\nWtedy B i C są różnego typu. B mówi „A jest kłamcą” – prawda, więc B jest rycerzem. Zatem C jest kłamcą. C mówi „Ja i A jesteśmy różnego typu” – oboje są kłamcami, więc zdanie jest fałszywe, co zgadza się z tym, że C kłamie. Wszystko spójne.\n\nJedyne rozwiązanie: A – kłamca, B – rycerz, C – kłamca.\n\nODPOWIEDŹ: B",
   "czas_s": 5.62,
   "usage": {
    "prompt_tokens": 233,
    "completion_tokens": 378,
    "total_tokens": 611,
    "cost": 0.016984,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.016984,
     "upstream_inference_prompt_cost": 0.001864,
     "upstream_inference_completions_cost": 0.01512
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.016984,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.016984,
   "ocena": {
    "odp": "B",
    "ok": true
   },
   "tokeny": {
    "wej": 233,
    "wyj": 378,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R1_rycerze",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Na wyspie mieszkają rycerze (zawsze mówią prawdę) i kłamcy (zawsze kłamią). Spotykasz trzy osoby.\nA mówi: „B i C są tego samego typu”.\nB mówi: „A jest kłamcą”.\nC mówi: „Ja i A jesteśmy różnego typu”.\nKto z nich jest rycerzem? Podaj litery wszystkich rycerzy (albo „nikt”).\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "**Rozumowanie:**\n\n1. **Wypowiedź C:** „Ja i A jesteśmy różnego typu”.\n   - Jeśli C jest rycerzem, to zdanie jest prawdziwe. Wtedy A jest innego typu niż C, czyli A jest kłamcą.\n   - Jeśli C jest kłamcą, to zdanie jest fałszywe. Wtedy A jest tego samego typu co C, czyli A też jest kłamcą.\n   - W obu przypadkach **A jest kłamcą**.\n\n2. **Wypowiedź B:** „A jest kłamcą”. To zdanie jest prawdziwe, więc **B jest rycerzem**.\n\n3. **Wypowiedź A:** „B i C są tego samego typu”. A jest kłamcą, więc to zdanie jest fałszywe. B i C są zatem różnego typu, a skoro B jest rycerzem, to **C jest kłamcą**.\n\n**Sprawdzenie:** C (kłamca) mówi, że on i A są różnego typu. W rzeczywistości obaj są kłamcami, więc zdanie jest fałszywe, co się zgadza.\n\nODPOWIEDŹ: B",
   "czas_s": 6.23,
   "usage": {
    "prompt_tokens": 233,
    "completion_tokens": 587,
    "total_tokens": 820,
    "cost": 0.012672,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.012672,
     "upstream_inference_prompt_cost": 0.000932,
     "upstream_inference_completions_cost": 0.01174
    },
    "completion_tokens_details": {
     "reasoning_tokens": 173,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.012672,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.012672,
   "ocena": {
    "odp": "B",
    "ok": true
   },
   "tokeny": {
    "wej": 233,
    "wyj": 587,
    "rozumowanie": 173
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R1_rycerze",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Na wyspie mieszkają rycerze (zawsze mówią prawdę) i kłamcy (zawsze kłamią). Spotykasz trzy osoby.\nA mówi: „B i C są tego samego typu”.\nB mówi: „A jest kłamcą”.\nC mówi: „Ja i A jesteśmy różnego typu”.\nKto z nich jest rycerzem? Podaj litery wszystkich rycerzy (albo „nikt”).\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Gdyby A był rycerzem, zdanie C byłoby prawdziwe dokładnie wtedy, gdy C byłby kłamcą — sprzeczność. Zatem A jest kłamcą.\n\nB mówi więc prawdę i jest rycerzem. Zdanie A jest fałszywe, czyli B i C są różnego typu. Zatem C jest kłamcą.\n\nODPOWIEDŹ: B",
   "czas_s": 7.11,
   "usage": {
    "prompt_tokens": 143,
    "completion_tokens": 221,
    "total_tokens": 364,
    "cost": 0.002496,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002496,
     "upstream_inference_prompt_cost": 0.000286,
     "upstream_inference_completions_cost": 0.00221
    },
    "completion_tokens_details": {
     "reasoning_tokens": 124,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002496,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002496,
   "ocena": {
    "odp": "B",
    "ok": true
   },
   "tokeny": {
    "wej": 143,
    "wyj": 221,
    "rozumowanie": 124
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R1_rycerze",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Na wyspie mieszkają rycerze (zawsze mówią prawdę) i kłamcy (zawsze kłamią). Spotykasz trzy osoby.\nA mówi: „B i C są tego samego typu”.\nB mówi: „A jest kłamcą”.\nC mówi: „Ja i A jesteśmy różnego typu”.\nKto z nich jest rycerzem? Podaj litery wszystkich rycerzy (albo „nikt”).\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Przeanalizujmy dwa możliwe przypadki dla osoby A:\n\n**Przypadek 1: A jest rycerzem (mówi prawdę).**\n* Skoro A mówi prawdę, to B jest kłamcą (bo B twierdzi, że A kłamie).\n* A mówi, że B i C są tego samego typu. Skoro B jest kłamcą, to C też musi być kłamcą.\n* Sprawdźmy wypowiedź C: C mówi „Ja i A jesteśmy różnego typu”. Skoro A to rycerz, a C to kłamca, to rzeczywiście są różnego typu. Oznacza to, że C powiedział prawdę, co jest sprzeczne z tym, że jest kłamcą. Ten przypadek jest więc niemożliwy.\n\n**Przypadek 2: A jest kłamcą (kłamie).**\n* Skoro A jest kłamcą, to B mówi prawdę („A jest kłamcą”), więc **B jest rycerzem**.\n* Wypowiedź A („B i C są tego samego typu”) jest fałszywa, więc B i C muszą być różnego typu. Skoro B jest rycerzem, to C musi być kłamcą.\n* Sprawdźmy wypowiedź C: C (kłamca) mówi „Ja i A jesteśmy różnego typu”. Ponieważ i C, i A są kłamcami, są tego samego typu. Zatem C skłamał, co zgadza się z jego rolą kłamcy.\n\nWszystkie warunki są spełnione: A to kłamca, B to rycerz, a C to kłamca.\n\nODPOWIEDŹ: B",
   "czas_s": 8.84,
   "usage": {
    "prompt_tokens": 160,
    "completion_tokens": 372,
    "total_tokens": 532,
    "cost": 0.00404,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.00404,
     "upstream_inference_prompt_cost": 0.00032,
     "upstream_inference_completions_cost": 0.00372
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.00404,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.00404,
   "ocena": {
    "odp": "B",
    "ok": true
   },
   "tokeny": {
    "wej": 160,
    "wyj": 372,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R2_cyfry",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Ile liczb całkowitych od 1 do 10 000 włącznie ma sumę cyfr równą dokładnie 10?\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Liczby od 1 do 9999 traktujemy jako ciągi czterech cyfr (z zerami wiodącymi): d1 + d2 + d3 + d4 = 10, gdzie 0 ≤ di ≤ 9.\n\nLiczba rozwiązań w nieujemnych liczbach całkowitych bez ograniczenia górnego: C(13, 3) = 286.\n\nOdejmujemy rozwiązania, w których któraś cyfra ≥ 10: to możliwe tylko gdy jedna cyfra równa się 10, a pozostałe 0 — 4 przypadki.\n\n286 − 4 = 282.\n\nLiczba 10 000 ma sumę cyfr 1, więc nie jest liczona. Liczba 0 (0000) ma sumę 0, więc też nie wpływa na wynik.\n\nODPOWIEDŹ: 282",
   "czas_s": 4.09,
   "usage": {
    "prompt_tokens": 119,
    "completion_tokens": 297,
    "total_tokens": 416,
    "cost": 0.012832,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.012832,
     "upstream_inference_prompt_cost": 0.000952,
     "upstream_inference_completions_cost": 0.01188
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.012832,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.012832,
   "ocena": {
    "odp": "282",
    "ok": true
   },
   "tokeny": {
    "wej": 119,
    "wyj": 297,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R2_cyfry",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Ile liczb całkowitych od 1 do 10 000 włącznie ma sumę cyfr równą dokładnie 10?\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "**Sprowadzenie do zliczania rozwiązań.** Liczby od 1 do 9999 zapisujemy jako czterocyfrowe ciągi z zerami wiodącymi (np. 37 → 0037). Szukamy liczby rozwiązań równania\n\na + b + c + d = 10, gdzie 0 ≤ a, b, c, d ≤ 9.\n\n**Krok 1. Rozwiązania bez górnego ograniczenia.** Liczba nieujemnych rozwiązań całkowitych wynosi C(13, 3) = 286.\n\n**Krok 2. Odrzucenie niedozwolonych przypadków.** Cyfra nie może przekroczyć 9. Gdyby któraś zmienna była ≥ 10, to przy sumie 10 musiałaby wynosić dokładnie 10, a pozostałe 0. Takie przypadki są 4, po jednym dla każdej pozycji.\n\nZostaje 286 − 4 = 282.\n\n**Krok 3. Sprawdzenie krańców przedziału.**\n- Ciąg 0000 (czyli liczba 0) ma sumę cyfr 0, więc nie jest wliczony.\n- Liczba 10 000 ma sumę cyfr 1, więc nic nie dodaje.\n\nODPOWIEDŹ: 282",
   "czas_s": 6.19,
   "usage": {
    "prompt_tokens": 119,
    "completion_tokens": 571,
    "total_tokens": 690,
    "cost": 0.011896,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.011896,
     "upstream_inference_prompt_cost": 0.000476,
     "upstream_inference_completions_cost": 0.01142
    },
    "completion_tokens_details": {
     "reasoning_tokens": 116,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.011896,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.011896,
   "ocena": {
    "odp": "282",
    "ok": true
   },
   "tokeny": {
    "wej": 119,
    "wyj": 571,
    "rozumowanie": 116
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R2_cyfry",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Ile liczb całkowitych od 1 do 10 000 włącznie ma sumę cyfr równą dokładnie 10?\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Liczby od 1 do 9999 zapisujemy jako cztery cyfry, dopuszczając zera na początku. Szukamy więc rozwiązań\n\\[\na+b+c+d=10,\\qquad 0\\le a,b,c,d\\le9.\n\\]\n\nBez ograniczenia górnego liczba nieujemnych rozwiązań wynosi\n\\[\n\\binom{10+4-1}{4-1}=\\binom{13}{3}=286.\n\\]\nOdejmujemy 4 niedozwolone rozwiązania, w których jedna zmienna wynosi 10, a pozostałe 0. Liczba 10 000 ma sumę cyfr 1, więc nie zwiększa wyniku.\n\nODPOWIEDŹ: 282",
   "czas_s": 5.86,
   "usage": {
    "prompt_tokens": 76,
    "completion_tokens": 234,
    "total_tokens": 310,
    "cost": 0.002492,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002492,
     "upstream_inference_prompt_cost": 0.000152,
     "upstream_inference_completions_cost": 0.00234
    },
    "completion_tokens_details": {
     "reasoning_tokens": 62,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002492,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002492,
   "ocena": {
    "odp": "282",
    "ok": true
   },
   "tokeny": {
    "wej": 76,
    "wyj": 234,
    "rozumowanie": 62
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R3_zegar",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Zegar analogowy ze wskazówkami poruszającymi się płynnie. Ile minut po godzinie 4:00 wskazówka minutowa i godzinowa pierwszy raz się pokryją? Podaj wynik jako dokładny ułamek lub liczbę mieszaną (np. 12 3/7).\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "O godzinie 4:00 wskazówka godzinowa jest na 120°, a minutowa na 0°.\n\nWskazówka minutowa porusza się 6°/min, godzinowa 0,5°/min, więc minutowa zbliża się z prędkością 5,5°/min.\n\nCzas do pokrycia: 120 / 5,5 = 240/11 = 21 9/11 minuty.\n\nODPOWIEDŹ: 21 9/11",
   "czas_s": 3.87,
   "usage": {
    "prompt_tokens": 184,
    "completion_tokens": 154,
    "total_tokens": 338,
    "cost": 0.007632,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.007632,
     "upstream_inference_prompt_cost": 0.001472,
     "upstream_inference_completions_cost": 0.00616
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.007632,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.007632,
   "ocena": {
    "odp": "21 9/11",
    "ok": true
   },
   "tokeny": {
    "wej": 184,
    "wyj": 154,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R3_zegar",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Zegar analogowy ze wskazówkami poruszającymi się płynnie. Ile minut po godzinie 4:00 wskazówka minutowa i godzinowa pierwszy raz się pokryją? Podaj wynik jako dokładny ułamek lub liczbę mieszaną (np. 12 3/7).\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "O godzinie 4:00 wskazówka godzinowa stoi na 4, czyli 120° od godziny 12. Wskazówka minutowa jest na 12, czyli na 0°.\n\nPrędkości wskazówek:\n- minutowa: 6° na minutę,\n- godzinowa: 0,5° na minutę.\n\nMinutowa dogania godzinową z prędkością względną 6° − 0,5° = 5,5° na minutę. Musi nadrobić 120°, więc:\n\n5,5·t = 120\nt = 240/11 = 21 9/11 minuty.\n\nWskazówki pierwszy raz pokryją się 21 9/11 minuty po 4:00, czyli około 4:21:49.\n\nODPOWIEDŹ: 21 9/11",
   "czas_s": 3.79,
   "usage": {
    "prompt_tokens": 184,
    "completion_tokens": 318,
    "total_tokens": 502,
    "cost": 0.007096,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.007096,
     "upstream_inference_prompt_cost": 0.000736,
     "upstream_inference_completions_cost": 0.00636
    },
    "completion_tokens_details": {
     "reasoning_tokens": 58,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.007096,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.007096,
   "ocena": {
    "odp": "21 9/11",
    "ok": true
   },
   "tokeny": {
    "wej": 184,
    "wyj": 318,
    "rozumowanie": 58
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R3_zegar",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Zegar analogowy ze wskazówkami poruszającymi się płynnie. Ile minut po godzinie 4:00 wskazówka minutowa i godzinowa pierwszy raz się pokryją? Podaj wynik jako dokładny ułamek lub liczbę mieszaną (np. 12 3/7).\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "O 4:00 wskazówka godzinowa wyprzedza minutową o \\(120^\\circ\\). Minutowa porusza się z prędkością \\(6^\\circ/\\text{min}\\), a godzinowa \\(0{,}5^\\circ/\\text{min}\\).\n\nCzas do pierwszego pokrycia wynosi więc:\n\\[\nt=\\frac{120}{6-0{,}5}=\\frac{240}{11}=21\\frac{9}{11}\\text{ min}.\n\\]\n\nODPOWIEDŹ: 21 9/11",
   "czas_s": 4.52,
   "usage": {
    "prompt_tokens": 115,
    "completion_tokens": 181,
    "total_tokens": 296,
    "cost": 0.00204,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.00204,
     "upstream_inference_prompt_cost": 0.00023,
     "upstream_inference_completions_cost": 0.00181
    },
    "completion_tokens_details": {
     "reasoning_tokens": 51,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.00204,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.00204,
   "ocena": {
    "odp": "21 9/11",
    "ok": true
   },
   "tokeny": {
    "wej": 115,
    "wyj": 181,
    "rozumowanie": 51
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R4_reszty",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Jaka jest najmniejsza dodatnia liczba całkowita, która z dzielenia przez 4 daje resztę 3, z dzielenia przez 5 resztę 1, a z dzielenia przez 9 resztę 7?\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Szukamy n takiego, że n ≡ 3 (mod 4), n ≡ 1 (mod 5), n ≡ 7 (mod 9).\n\nLiczby dające resztę 7 z dzielenia przez 9: 7, 16, 25, 34, 43, 52, 61, 70, 79, 88, 97, 106, ...\n\nSpośród nich resztę 1 z dzielenia przez 5 dają: 16, 61, 106, 151, ... (co 45).\n\nSprawdzamy resztę z dzielenia przez 4:\n- 16 mod 4 = 0\n- 61 mod 4 = 1\n- 106 mod 4 = 2\n- 151 mod 4 = 3 ✓\n\nSprawdzenie: 151 = 4·37 + 3, 151 = 5·30 + 1, 151 = 9·16 + 7.\n\nODPOWIEDŹ: 151",
   "czas_s": 3.9,
   "usage": {
    "prompt_tokens": 155,
    "completion_tokens": 306,
    "total_tokens": 461,
    "cost": 0.01348,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.01348,
     "upstream_inference_prompt_cost": 0.00124,
     "upstream_inference_completions_cost": 0.01224
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.01348,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.01348,
   "ocena": {
    "odp": "151",
    "ok": true
   },
   "tokeny": {
    "wej": 155,
    "wyj": 306,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R2_cyfry",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Ile liczb całkowitych od 1 do 10 000 włącznie ma sumę cyfr równą dokładnie 10?\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Rozważamy liczby całkowite z przedziału od $1$ do $10000$. \n\n1. Liczba $10000$ ma sumę cyfr równą $1 + 0 + 0 + 0 + 0 = 1$, więc nie spełnia warunku.\n2. Pozostałe liczby (od $1$ do $9999$) możemy zapisać jako czterocyfrowe ciągi (uzupełniając wiodącymi zerami, np. $7$ to $0007$) postaci $c_1c_2c_3c_4$, gdzie każda cyfra $c_i \\in \\{0, 1, \\dots, 9\\}$.\n\nSzukamy liczby rozwiązań równania w liczbach całkowitych nieujemnych:\n$c_1 + c_2 + c_3 + c_4 = 10$\nprzy ograniczeniu, że każda cyfra jest maksymalnie równa $9$ ($c_i \\le 9$).\n\nZe wzoru na kombinacje z powtórzeniami (metoda \"kul i przegródek\"), liczba wszystkich rozwiązań tego równania w liczbach całkowitych nieujemnych wynosi:\n$\\binom{10 + 4 - 1}{4 - 1} = \\binom{13}{3} = \\frac{13 \\cdot 12 \\cdot 11}{3 \\cdot 2 \\cdot 1} = 286$\n\nMusimy jednak odjąć te rozwiązania, w których którakolwiek ze zmiennych przekracza $9$ (czyli wynosi $10$, bo suma to $10$). Takie przypadki są dokładnie $4$:\n$(10, 0, 0, 0)$, $(0, 10, 0, 0)$, $(0, 0, 10, 0)$ oraz $(0, 0, 0, 10)$.\n\nOstateczna liczba takich liczb to:\n$286 - 4 = 282$\n\nODPOWIEDŹ: 282",
   "czas_s": 9.05,
   "usage": {
    "prompt_tokens": 94,
    "completion_tokens": 467,
    "total_tokens": 561,
    "cost": 0.004858,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.004858,
     "upstream_inference_prompt_cost": 0.000188,
     "upstream_inference_completions_cost": 0.00467
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.004858,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.004858,
   "ocena": {
    "odp": "282",
    "ok": true
   },
   "tokeny": {
    "wej": 94,
    "wyj": 467,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R5_wtorek",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Rodzina ma dwoje dzieci. Wiadomo, że co najmniej jedno z nich to chłopiec urodzony we wtorek. Zakładamy, że płeć i dzień tygodnia urodzenia są niezależne i równo prawdopodobne. Jakie jest prawdopodobieństwo, że oboje dzieci to chłopcy? Podaj dokładny ułamek.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Każde dziecko ma 14 równo prawdopodobnych kombinacji (płeć × dzień tygodnia), więc dla dwojga dzieci jest 14 · 14 = 196 równo prawdopodobnych przypadków.\n\nPrzypadki, w których co najmniej jedno dziecko to chłopiec urodzony we wtorek: 196 − 13 · 13 = 196 − 169 = 27.\n\nWśród nich przypadki, gdy oboje to chłopcy: wszystkich par chłopców jest 7 · 7 = 49, a par, w których żaden nie urodził się we wtorek, jest 6 · 6 = 36, zatem 49 − 36 = 13.\n\nPrawdopodobieństwo warunkowe: 13/27.\n\nODPOWIEDŹ: 13/27",
   "czas_s": 3.64,
   "usage": {
    "prompt_tokens": 212,
    "completion_tokens": 278,
    "total_tokens": 490,
    "cost": 0.012816,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.012816,
     "upstream_inference_prompt_cost": 0.001696,
     "upstream_inference_completions_cost": 0.01112
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.012816,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.012816,
   "ocena": {
    "odp": "13/27",
    "ok": true
   },
   "tokeny": {
    "wej": 212,
    "wyj": 278,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R4_reszty",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Jaka jest najmniejsza dodatnia liczba całkowita, która z dzielenia przez 4 daje resztę 3, z dzielenia przez 5 resztę 1, a z dzielenia przez 9 resztę 7?\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "**Krok 1.** Łączę dwa pierwsze warunki: x ≡ 3 (mod 4) i x ≡ 1 (mod 5).\n\nLiczby dające resztę 1 z dzielenia przez 5 to 1, 6, 11, … Pierwsza z nich dająca resztę 3 z dzielenia przez 4 to 11. Ponieważ NWW(4, 5) = 20, otrzymuję x ≡ 11 (mod 20), czyli x = 11 + 20k.\n\n**Krok 2.** Dokładam warunek x ≡ 7 (mod 9).\n\nMamy 11 ≡ 2 (mod 9) oraz 20 ≡ 2 (mod 9). Zatem 2 + 2k ≡ 7 (mod 9), czyli 2k ≡ 5 (mod 9).\n\nOdwrotnością 2 modulo 9 jest 5, bo 2 · 5 = 10 ≡ 1 (mod 9). Stąd k ≡ 25 ≡ 7 (mod 9).\n\nNajmniejsze nieujemne k to 7, więc x = 11 + 140 = 151.\n\n**Sprawdzenie:**\n- 151 = 4·37 + 3, więc reszta z dzielenia przez 4 wynosi 3.\n- 151 = 5·30 + 1, więc reszta z dzielenia przez 5 wynosi 1.\n- 151 = 9·16 + 7, więc reszta z dzielenia przez 9 wynosi 7.\n\nWszystkie rozwiązania mają postać 151 + 180m, więc 151 jest najmniejszym dodatnim.\n\nODPOWIEDŹ: 151",
   "czas_s": 7.68,
   "usage": {
    "prompt_tokens": 155,
    "completion_tokens": 749,
    "total_tokens": 904,
    "cost": 0.0156,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.0156,
     "upstream_inference_prompt_cost": 0.00062,
     "upstream_inference_completions_cost": 0.01498
    },
    "completion_tokens_details": {
     "reasoning_tokens": 169,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.0156,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.0156,
   "ocena": {
    "odp": "151",
    "ok": true
   },
   "tokeny": {
    "wej": 155,
    "wyj": 749,
    "rozumowanie": 169
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R3_zegar",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Zegar analogowy ze wskazówkami poruszającymi się płynnie. Ile minut po godzinie 4:00 wskazówka minutowa i godzinowa pierwszy raz się pokryją? Podaj wynik jako dokładny ułamek lub liczbę mieszaną (np. 12 3/7).\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Wskazówka minutowa porusza się z prędkością $6^\\circ$ na minutę ($360^\\circ / 60 \\text{ min}$).\nWskazówka godzinowa porusza się z prędkością $0{,}5^\\circ$ na minutę ($30^\\circ / 60 \\text{ min}$).\n\nO godzinie 4:00 wskazówka godzinowa jest na pozycji $120^\\circ$ ($4 \\times 30^\\circ$), a minutowa na pozycji $0^\\circ$.\n\nNiech $t$ oznacza liczbę minut po godzinie 4:00. Wskazówki pokryją się, gdy ich kąty będą równe:\n$$6t = 120 + 0{,}5t$$\n$$5{,}5t = 120$$\n$$\\frac{11}{2}t = 120$$\n$$11t = 240$$\n$$t = \\frac{240}{11} = 21 \\frac{9}{11}$$\n\nODPOWIEDŹ: 21 9/11",
   "czas_s": 7.29,
   "usage": {
    "prompt_tokens": 127,
    "completion_tokens": 258,
    "total_tokens": 385,
    "cost": 0.002834,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002834,
     "upstream_inference_prompt_cost": 0.000254,
     "upstream_inference_completions_cost": 0.00258
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002834,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002834,
   "ocena": {
    "odp": "21 9/11",
    "ok": true
   },
   "tokeny": {
    "wej": 127,
    "wyj": 258,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R6_piatek13",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Ile razy w roku 2027 piątek wypada trzynastego dnia miesiąca? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Rok 2027 nie jest przestępny. 1 stycznia 2027 to piątek (1 stycznia 2026 to czwartek, a 2026 ma 365 dni, więc przesunięcie o 1 dzień).\n\nZatem 13 stycznia 2027 = 1 stycznia + 12 dni → piątek + 5 dni = środa.\n\nPrzesunięcia dnia tygodnia dla 13. dnia kolejnych miesięcy (rok zwykły):\n- Styczeń: środa\n- Luty (+3): sobota\n- Marzec (+0): sobota\n- Kwiecień (+3): wtorek\n- Maj (+2): czwartek\n- Czerwiec (+3): niedziela\n- Lipiec (+2): wtorek\n- Sierpień (+3): piątek ✔\n- Wrzesień (+3): poniedziałek\n- Październik (+2): środa\n- Listopad (+3): sobota\n- Grudzień (+2): poniedziałek\n\nPiątek trzynastego wypada tylko w sierpniu 2027.\n\nODPOWIEDŹ: 1",
   "czas_s": 4.35,
   "usage": {
    "prompt_tokens": 116,
    "completion_tokens": 377,
    "total_tokens": 493,
    "cost": 0.016008,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.016008,
     "upstream_inference_prompt_cost": 0.000928,
     "upstream_inference_completions_cost": 0.01508
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.016008,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.016008,
   "ocena": {
    "odp": "1",
    "ok": true
   },
   "tokeny": {
    "wej": 116,
    "wyj": 377,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R4_reszty",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Jaka jest najmniejsza dodatnia liczba całkowita, która z dzielenia przez 4 daje resztę 3, z dzielenia przez 5 resztę 1, a z dzielenia przez 9 resztę 7?\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Liczby dające resztę 3 przy dzieleniu przez 4 i resztę 1 przy dzieleniu przez 5 mają postać\n\\[\nn=20k+11.\n\\]\nWarunek dzielenia przez 9 daje\n\\[\n20k+11\\equiv7\\pmod9\n\\quad\\Rightarrow\\quad 2k\\equiv5\\pmod9\n\\quad\\Rightarrow\\quad k\\equiv7\\pmod9.\n\\]\nNajmniejsze dopuszczalne \\(k\\) to 7, więc \\(n=20\\cdot7+11=151\\).\n\nODPOWIEDŹ: 151",
   "czas_s": 8.79,
   "usage": {
    "prompt_tokens": 101,
    "completion_tokens": 242,
    "total_tokens": 343,
    "cost": 0.002622,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002622,
     "upstream_inference_prompt_cost": 0.000202,
     "upstream_inference_completions_cost": 0.00242
    },
    "completion_tokens_details": {
     "reasoning_tokens": 89,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002622,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002622,
   "ocena": {
    "odp": "151",
    "ok": true
   },
   "tokeny": {
    "wej": 101,
    "wyj": 242,
    "rozumowanie": 89
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R7_pl_tuzin",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Ile to jest pół tuzina tuzinów podzielone przez pół? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Tuzin tuzinów = 12 × 12 = 144.\nPół tuzina tuzinów = 6 × 12 = 72.\nDzielenie przez pół (czyli przez 1/2) = mnożenie przez 2: 72 ÷ 0,5 = 144.\n\nODPOWIEDŹ: 144",
   "czas_s": 3.49,
   "usage": {
    "prompt_tokens": 110,
    "completion_tokens": 113,
    "total_tokens": 223,
    "cost": 0.0054,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.0054,
     "upstream_inference_prompt_cost": 0.00088,
     "upstream_inference_completions_cost": 0.00452
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.0054,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.0054,
   "ocena": {
    "odp": "144",
    "ok": true
   },
   "tokeny": {
    "wej": 110,
    "wyj": 113,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R5_wtorek",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Rodzina ma dwoje dzieci. Wiadomo, że co najmniej jedno z nich to chłopiec urodzony we wtorek. Zakładamy, że płeć i dzień tygodnia urodzenia są niezależne i równo prawdopodobne. Jakie jest prawdopodobieństwo, że oboje dzieci to chłopcy? Podaj dokładny ułamek.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "**Model:** każde dziecko ma jeden z 14 równo prawdopodobnych „typów” (płeć × dzień tygodnia). Dla dwojga dzieci jest więc 14·14 = 196 równo prawdopodobnych par.\n\n**Warunek „co najmniej jedno to chłopiec urodzony we wtorek”:**\n- Typów innych niż chłopiec-wtorek jest 13.\n- Par bez żadnego chłopca-wtorek jest 13² = 169.\n- Par spełniających warunek jest więc 196 − 169 = 27.\n\n**Oboje chłopcy i co najmniej jeden urodzony we wtorek:**\n- Par dwóch chłopców jest 7·7 = 49.\n- Par dwóch chłopców, z których żaden nie urodził się we wtorek, jest 6·6 = 36.\n- Szukanych par jest więc 49 − 36 = 13.\n\n**Prawdopodobieństwo:** P = 13/27.\n\nODPOWIEDŹ: 13/27",
   "czas_s": 5.55,
   "usage": {
    "prompt_tokens": 212,
    "completion_tokens": 443,
    "total_tokens": 655,
    "cost": 0.009708,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.009708,
     "upstream_inference_prompt_cost": 0.000848,
     "upstream_inference_completions_cost": 0.00886
    },
    "completion_tokens_details": {
     "reasoning_tokens": 89,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.009708,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.009708,
   "ocena": {
    "odp": "13/27",
    "ok": true
   },
   "tokeny": {
    "wej": 212,
    "wyj": 443,
    "rozumowanie": 89
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R5_wtorek",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Rodzina ma dwoje dzieci. Wiadomo, że co najmniej jedno z nich to chłopiec urodzony we wtorek. Zakładamy, że płeć i dzień tygodnia urodzenia są niezależne i równo prawdopodobne. Jakie jest prawdopodobieństwo, że oboje dzieci to chłopcy? Podaj dokładny ułamek.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Każde dziecko ma 14 jednakowo prawdopodobnych kombinacji płci i dnia urodzenia. Dla dwojga dzieci mamy więc \\(14^2=196\\) możliwości.\n\nCo najmniej jeden chłopiec urodzony we wtorek występuje w\n\\[\n14^2-13^2=27\n\\]\nprzypadkach.\n\nPrzypadków, w których oboje dzieci to chłopcy i co najmniej jeden urodził się we wtorek, jest\n\\[\n7^2-6^2=13.\n\\]\n\nZatem szukane prawdopodobieństwo wynosi \\(\\frac{13}{27}\\).\n\nODPOWIEDŹ: 13/27",
   "czas_s": 4.25,
   "usage": {
    "prompt_tokens": 129,
    "completion_tokens": 185,
    "total_tokens": 314,
    "cost": 0.002108,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002108,
     "upstream_inference_prompt_cost": 0.000258,
     "upstream_inference_completions_cost": 0.00185
    },
    "completion_tokens_details": {
     "reasoning_tokens": 24,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002108,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002108,
   "ocena": {
    "odp": "13/27",
    "ok": true
   },
   "tokeny": {
    "wej": 129,
    "wyj": 185,
    "rozumowanie": 24
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R8_pl_litery",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Ile razy w zdaniu „Zażółć gęślą jaźń” występuje litera „z” polskiego alfabetu (wielkość liter nie ma znaczenia; ż i ź to osobne litery)? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Przeanalizujmy zdanie litera po literze:\n\n**Zażółć**: Z, a, ż, ó, ł, ć → „Z” (1 raz; „ż” to osobna litera)\n\n**gęślą**: g, ę, ś, l, ą → brak „z”\n\n**jaźń**: j, a, ź, ń → brak „z” („ź” to osobna litera)\n\nLitera „z” występuje więc tylko raz – na początku słowa „Zażółć”.\n\nODPOWIEDŹ: 1",
   "czas_s": 3.82,
   "usage": {
    "prompt_tokens": 157,
    "completion_tokens": 175,
    "total_tokens": 332,
    "cost": 0.008256,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.008256,
     "upstream_inference_prompt_cost": 0.001256,
     "upstream_inference_completions_cost": 0.007
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.008256,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.008256,
   "ocena": {
    "odp": "1",
    "ok": true
   },
   "tokeny": {
    "wej": 157,
    "wyj": 175,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R4_reszty",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Jaka jest najmniejsza dodatnia liczba całkowita, która z dzielenia przez 4 daje resztę 3, z dzielenia przez 5 resztę 1, a z dzielenia przez 9 resztę 7?\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Szukamy najmniejszej dodatniej liczby całkowitej $x$, która spełnia układ kongruencji:\n1) $x \\equiv 3 \\pmod 4$\n2) $x \\equiv 1 \\pmod 5$\n3) $x \\equiv 7 \\pmod 9$\n\nZauważmy, że w pierwszym i trzecim warunku reszta jest o 1 mniejsza od dzielnika, co możemy zapisać jako:\n$x \\equiv -1 \\pmod 4$\n$x \\equiv -2 \\pmod 9$\n\nRozwiążmy to metodą podstawiania, zaczynając od największego dzielnika (9). Liczby dające resztę 7 z dzielenia przez 9 to:\n7, 16, 25, 34, 43, 52, 61, 70, 79, ...\n\nTeraz wybierzmy z nich te, które dają resztę 1 z dzielenia przez 5 (czyli kończą się cyfrą 1 lub 6):\n- 16 (16 : 4 = 4 reszta 0 – odpada)\n- 61 (61 : 4 = 15 reszta 1 – odpada)\n\nSzukamy dalej, dodając za każdym razem $45$ (ponieważ $\\text{NWW}(5, 9) = 45$):\n- $16 + 45 = 61$ (sprawdzone)\n- $61 + 45 = 106$ (106 : 4 = 26 reszta 2 – odpada)\n- $106 + 45 = 151$ (151 : 4 = 37 reszta 3 – pasuje!)\n\nSprawdzenie dla liczby 151:\n- $151 = 37 \\cdot 4 + 3$\n- $151 = 30 \\cdot 5 + 1$\n- $151 = 16 \\cdot 9 + 7$\n\nODPOWIEDŹ: 151",
   "czas_s": 8.43,
   "usage": {
    "prompt_tokens": 114,
    "completion_tokens": 483,
    "total_tokens": 597,
    "cost": 0.005058,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.005058,
     "upstream_inference_prompt_cost": 0.000228,
     "upstream_inference_completions_cost": 0.00483
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.005058,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.005058,
   "ocena": {
    "odp": "151",
    "ok": true
   },
   "tokeny": {
    "wej": 114,
    "wyj": 483,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R6_piatek13",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Ile razy w roku 2027 piątek wypada trzynastego dnia miesiąca? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "**Rozumowanie:**\n\n1. Rok 2026 nie jest przestępny, a 1 stycznia 2026 to czwartek. Zatem 1 stycznia 2027 to piątek.\n2. 13 stycznia 2027 wypada 12 dni później. 12 mod 7 = 5, więc piątek + 5 dni = **środa**.\n3. Kolejne 13. dni miesiąca przesuwają się o liczbę dni poprzedniego miesiąca mod 7. Rok 2027 nie jest przestępny, więc luty ma 28 dni.\n\n| Miesiąc | Przesunięcie | Dzień tygodnia 13. |\n|---|---|---|\n| Styczeń | – | środa |\n| Luty | +3 | sobota |\n| Marzec | +0 | sobota |\n| Kwiecień | +3 | wtorek |\n| Maj | +2 | czwartek |\n| Czerwiec | +3 | niedziela |\n| Lipiec | +2 | wtorek |\n| Sierpień | +3 | **piątek** |\n| Wrzesień | +3 | poniedziałek |\n| Październik | +2 | środa |\n| Listopad | +3 | sobota |\n| Grudzień | +2 | poniedziałek |\n\nPiątek 13. występuje w 2027 roku tylko raz: 13 sierpnia.\n\nODPOWIEDŹ: 1",
   "czas_s": 7.14,
   "usage": {
    "prompt_tokens": 116,
    "completion_tokens": 764,
    "total_tokens": 880,
    "cost": 0.015744,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.015744,
     "upstream_inference_prompt_cost": 0.000464,
     "upstream_inference_completions_cost": 0.01528
    },
    "completion_tokens_details": {
     "reasoning_tokens": 286,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.015744,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.015744,
   "ocena": {
    "odp": "1",
    "ok": true
   },
   "tokeny": {
    "wej": 116,
    "wyj": 764,
    "rozumowanie": 286
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R6_piatek13",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Ile razy w roku 2027 piątek wypada trzynastego dnia miesiąca? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "W 2027 roku piątek trzynastego wypada tylko raz — 13 sierpnia.\n\nODPOWIEDŹ: 1",
   "czas_s": 6.02,
   "usage": {
    "prompt_tokens": 72,
    "completion_tokens": 110,
    "total_tokens": 182,
    "cost": 0.001244,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.001244,
     "upstream_inference_prompt_cost": 0.000144,
     "upstream_inference_completions_cost": 0.0011
    },
    "completion_tokens_details": {
     "reasoning_tokens": 73,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.001244,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.001244,
   "ocena": {
    "odp": "1",
    "ok": true
   },
   "tokeny": {
    "wej": 72,
    "wyj": 110,
    "rozumowanie": 73
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R7_pl_tuzin",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Ile to jest pół tuzina tuzinów podzielone przez pół? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Pół tuzina tuzinów to 6 × 12 = 72.\n\nPodzielenie przez pół oznacza dzielenie przez 1/2, czyli mnożenie przez 2: 72 ÷ 0,5 = 144.\n\n(To nie to samo co podzielenie na pół, które dałoby 36.)\n\nODPOWIEDŹ: 144",
   "czas_s": 3.71,
   "usage": {
    "prompt_tokens": 110,
    "completion_tokens": 208,
    "total_tokens": 318,
    "cost": 0.0046,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.0046,
     "upstream_inference_prompt_cost": 0.00044,
     "upstream_inference_completions_cost": 0.00416
    },
    "completion_tokens_details": {
     "reasoning_tokens": 84,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.0046,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.0046,
   "ocena": {
    "odp": "144",
    "ok": true
   },
   "tokeny": {
    "wej": 110,
    "wyj": 208,
    "rozumowanie": 84
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R7_pl_tuzin",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Ile to jest pół tuzina tuzinów podzielone przez pół? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Pół tuzina tuzinów to 6 × 12 = 72. Dzielenie przez pół podwaja liczbę: 72 ÷ 0,5 = 144.\n\nODPOWIEDŹ: 144",
   "czas_s": 4.77,
   "usage": {
    "prompt_tokens": 67,
    "completion_tokens": 81,
    "total_tokens": 148,
    "cost": 0.000944,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.000944,
     "upstream_inference_prompt_cost": 0.000134,
     "upstream_inference_completions_cost": 0.00081
    },
    "completion_tokens_details": {
     "reasoning_tokens": 26,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.000944,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.000944,
   "ocena": {
    "odp": "144",
    "ok": true
   },
   "tokeny": {
    "wej": 67,
    "wyj": 81,
    "rozumowanie": 26
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R8_pl_litery",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Ile razy w zdaniu „Zażółć gęślą jaźń” występuje litera „z” polskiego alfabetu (wielkość liter nie ma znaczenia; ż i ź to osobne litery)? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Wypiszmy litery zdania **„Zażółć gęślą jaźń”**:\n\n- **Zażółć**: Z, a, ż, ó, ł, ć. Mamy tu jedno „z” (wielka litera Z) oraz „ż”, które się nie liczy.\n- **gęślą**: g, ę, ś, l, ą. Nie ma tu „z”.\n- **jaźń**: j, a, ź, ń. Jest tu „ź”, które się nie liczy.\n\nZwykła litera „z” występuje więc tylko raz.\n\nODPOWIEDŹ: 1",
   "czas_s": 3.44,
   "usage": {
    "prompt_tokens": 157,
    "completion_tokens": 267,
    "total_tokens": 424,
    "cost": 0.005968,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.005968,
     "upstream_inference_prompt_cost": 0.000628,
     "upstream_inference_completions_cost": 0.00534
    },
    "completion_tokens_details": {
     "reasoning_tokens": 64,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.005968,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.005968,
   "ocena": {
    "odp": "1",
    "ok": true
   },
   "tokeny": {
    "wej": 157,
    "wyj": 267,
    "rozumowanie": 64
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R5_wtorek",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Rodzina ma dwoje dzieci. Wiadomo, że co najmniej jedno z nich to chłopiec urodzony we wtorek. Zakładamy, że płeć i dzień tygodnia urodzenia są niezależne i równo prawdopodobne. Jakie jest prawdopodobieństwo, że oboje dzieci to chłopcy? Podaj dokładny ułamek.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Aby rozwiązać to zadanie, przyjmijmy, że każde dziecko można opisać parą (płeć, dzień tygodnia). Płeć ma 2 możliwości (Chłopiec, Dziewczynka), a dzień tygodnia 7 możliwości. Łącznie dla jednego dziecka jest $2 \\times 7 = 14$ równie prawdopodobnych kombinacji.\n\nDla dwojga dzieci przestrzeń zdarzeń elementarnych liczy $14 \\times 14 = 196$ równoprawdopodobnych możliwości.\n\nNiech:\n* $B$ oznacza zdarzenie, że co najmniej jedno dziecko to chłopiec urodzony we wtorek.\n* $A$ oznacza zdarzenie, że oboje dzieci to chłopcy.\n\nSzukamy prawdopodobieństwa warunkowego $P(A|B) = \\frac{|A \\cap B|}{|B|}$.\n\n1. **Liczba zdarzeń sprzyjających $B$ (co najmniej jeden chłopiec z wtorku):**\n   * Pierwsze dziecko to chłopiec z wtorku (1 możliwość), a drugie to dowolne dziecko (14 możliwości) – daje to 14 par.\n   * Drugie dziecko to chłopiec z wtorku (1 możliwość), a pierwsze to dowolne dziecko (14 możliwości) – daje to 14 par.\n   * Sytuację, w której oboje dzieci to chłopcy z wtorku, policzyliśmy podwójnie, więc musimy odjąć 1.\n   * Razem $|B| = 14 + 14 - 1 = 27$.\n\n2. **Liczba zdarzeń sprzyjających $A \\cap B$ (oboje to chłopcy i co najmniej jeden jest z wtorku):**\n   * Pierwsze dziecko to chłopiec z wtorku (1 możliwość), a drugie to dowolny chłopiec (7 możliwości) – daje to 7 par.\n   * Drugie dziecko to chłopiec z wtorku (1 możliwość), a pierwsze to dowolny chłopiec (7 możliwości) – daje to 7 par.\n   * Odejmujemy 1 wspólną sytuację (obaj chłopcy z wtorku).\n   * Razem $|A \\cap B| = 7 + 7 - 1 = 13$.\n\nPrawdopodobieństwo wynosi zatem $\\frac{13}{27}$.\n\nODPOWIEDŹ: 13/27",
   "czas_s": 10.29,
   "usage": {
    "prompt_tokens": 137,
    "completion_tokens": 519,
    "total_tokens": 656,
    "cost": 0.005464,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.005464,
     "upstream_inference_prompt_cost": 0.000274,
     "upstream_inference_completions_cost": 0.00519
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.005464,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.005464,
   "ocena": {
    "odp": "13/27",
    "ok": true
   },
   "tokeny": {
    "wej": 137,
    "wyj": 519,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R8_pl_litery",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Ile razy w zdaniu „Zażółć gęślą jaźń” występuje litera „z” polskiego alfabetu (wielkość liter nie ma znaczenia; ż i ź to osobne litery)? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Litera „z” występuje tylko na początku zdania; „ż” i „ź” nie są liczone.\n\nODPOWIEDŹ: 1",
   "czas_s": 2.52,
   "usage": {
    "prompt_tokens": 102,
    "completion_tokens": 99,
    "total_tokens": 201,
    "cost": 0.001194,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.001194,
     "upstream_inference_prompt_cost": 0.000204,
     "upstream_inference_completions_cost": 0.00099
    },
    "completion_tokens_details": {
     "reasoning_tokens": 59,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.001194,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.001194,
   "ocena": {
    "odp": "1",
    "ok": true
   },
   "tokeny": {
    "wej": 102,
    "wyj": 99,
    "rozumowanie": 59
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R6_piatek13",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Ile razy w roku 2027 piątek wypada trzynastego dnia miesiąca? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Piątek trzynastego wypada tylko w miesiącach, które zaczynają się w niedzielę (ponieważ $1 + 12 = 13$, a 12 dni po niedzieli to piątek).\n\nRok 2027 jest rokiem zwykłym (nieprzestępnym) i zaczyna się w piątek (1 stycznia 2027 r.). Licząc przesunięcia dni tygodnia dla kolejnych miesięcy (reszta z dzielenia liczby dni miesiąca przez 7), pierwsze dni miesięcy w 2027 roku wypadają następująco:\n\n* Styczeń: piątek\n* Luty: poniedziałek\n* Marzec: poniedziałek\n* Kwiecień: czwartek\n* Maj: sobota\n* Czerwiec: wtorek\n* Lipiec: czwartek\n* **Sierpień: niedziela** (13 sierpnia to piątek)\n* Wrzesień: środa\n* Październik: piątek\n* Listopad: poniedziałek\n* Grudzień: środa\n\nW roku 2027 tylko jeden miesiąc (sierpień) zaczyna się w niedzielę.\n\nODPOWIEDŹ: 1",
   "czas_s": 7.63,
   "usage": {
    "prompt_tokens": 89,
    "completion_tokens": 269,
    "total_tokens": 358,
    "cost": 0.002868,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002868,
     "upstream_inference_prompt_cost": 0.000178,
     "upstream_inference_completions_cost": 0.00269
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002868,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002868,
   "ocena": {
    "odp": "1",
    "ok": true
   },
   "tokeny": {
    "wej": 89,
    "wyj": 269,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R7_pl_tuzin",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Ile to jest pół tuzina tuzinów podzielone przez pół? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Tok rozumowania:\n1. Jeden tuzin to 12.\n2. Pół tuzina to 6.\n3. Pół tuzina tuzinów to 6 razy 12, co daje 72.\n4. Dzielenie przez pół (czyli przez 0,5) jest równoznaczne z mnożeniem przez 2 (w przeciwieństwie do dzielenia *na* pół). \n5. 72 podzielone przez 0,5 to 144.\n\nODPOWIEDŹ: 144",
   "czas_s": 5.84,
   "usage": {
    "prompt_tokens": 82,
    "completion_tokens": 127,
    "total_tokens": 209,
    "cost": 0.001434,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.001434,
     "upstream_inference_prompt_cost": 0.000164,
     "upstream_inference_completions_cost": 0.00127
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.001434,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.001434,
   "ocena": {
    "odp": "144",
    "ok": true
   },
   "tokeny": {
    "wej": 82,
    "wyj": 127,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "rozum",
   "zadanie": "R8_pl_litery",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Ile razy w zdaniu „Zażółć gęślą jaźń” występuje litera „z” polskiego alfabetu (wielkość liter nie ma znaczenia; ż i ź to osobne litery)? Podaj liczbę.\n\nMożesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Przeanalizujmy zdanie „Zażółć gęślą jaźń” słowo po słowie, szukając litery „z” (bez znaków diakrytycznych):\n\n1. **Zażółć** – pierwsza litera to „Z” (liczymy jako 1). Trzecia litera to „ż”, która jest osobną literą w polskim alfabecie.\n2. **gęślą** – brak litery „z”.\n3. **jaźń** – trzecia litera to „ź”, która również jest osobną literą.\n\nLitera „z” występuje w tym zdaniu tylko na samym początku.\n\nODPOWIEDŹ: 1",
   "czas_s": 10.59,
   "usage": {
    "prompt_tokens": 114,
    "completion_tokens": 159,
    "total_tokens": 273,
    "cost": 0.001818,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.001818,
     "upstream_inference_prompt_cost": 0.000228,
     "upstream_inference_completions_cost": 0.00159
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.001818,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.001818,
   "ocena": {
    "odp": "1",
    "ok": true
   },
   "tokeny": {
    "wej": 114,
    "wyj": 159,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P1_urzad",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Napisz e-mail do Urzędu Skarbowego w imieniu podatnika (Jan Kowalski). Sytuacja: złożył zeznanie PIT-37 za 2025 rok 14 lutego 2026 r. przez e-Urząd Skarbowy, wykazał nadpłatę 1 284 zł, a zwrotu do dziś nie dostał (ustawowy termin to 45 dni). Poproś o informację o stanie sprawy i termin zwrotu, podaj numer konta w formie placeholdera. Ton urzędowy, ale nie archaiczny. 120-200 słów, z tematem wiadomości.",
   "odpowiedz": "**Temat:** Zapytanie o stan sprawy – zwrot nadpłaty z zeznania PIT-37 za 2025 r.\n\nSzanowni Państwo,\n\n14 lutego 2026 r. złożyłem za pośrednictwem e-Urzędu Skarbowego zeznanie PIT-37 za rok 2025, w którym wykazałem nadpłatę podatku w wysokości 1 284 zł.\n\nZgodnie z art. 77 § 1 pkt 5 Ordynacji podatkowej zwrot nadpłaty wynikającej z zeznania złożonego drogą elektroniczną powinien nastąpić w terminie 45 dni od dnia jego złożenia. Termin ten już upłynął, a do dnia dzisiejszego nie otrzymałem zwrotu ani żadnej informacji o przyczynach opóźnienia.\n\nW związku z tym proszę o:\n\n1. informację o aktualnym stanie sprawy,\n2. wskazanie przyczyny opóźnienia,\n3. podanie przewidywanego terminu zwrotu nadpłaty.\n\nZwrot proszę przekazać na rachunek bankowy: [numer rachunku bankowego].\n\nJeżeli do rozpatrzenia sprawy potrzebne są dodatkowe wyjaśnienia lub dokumenty, proszę o kontakt, niezwłocznie je dostarczę.\n\nZ poważaniem\nJan Kowalski\nPESEL: [numer PESEL]\nAdres: [adres zamieszkania]\nTel.: [numer telefonu]",
   "czas_s": 6.84,
   "usage": {
    "prompt_tokens": 227,
    "completion_tokens": 544,
    "total_tokens": 771,
    "cost": 0.023576,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.023576,
     "upstream_inference_prompt_cost": 0.001816,
     "upstream_inference_completions_cost": 0.02176
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.023576,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.023576,
   "slowa": 143,
   "reczna_ocena": {
    "bledy_jezykowe": 1,
    "uwagi": "Poprawny, rzeczowy. Błąd merytoryczny: powołuje art. 77 § 1 pkt 5 Ordynacji podatkowej, a 45 dni dla zeznań elektronicznych to pkt 5a (pkt 5 to 3 miesiące dla papieru). Błąd językowy: przecinek zamiast średnika/kropki w 'proszę o kontakt, niezwłocznie je dostarczę'. W temacie półpauza zamiast dywizu."
   },
   "tokeny": {
    "wej": 227,
    "wyj": 544,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P1_urzad",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Napisz e-mail do Urzędu Skarbowego w imieniu podatnika (Jan Kowalski). Sytuacja: złożył zeznanie PIT-37 za 2025 rok 14 lutego 2026 r. przez e-Urząd Skarbowy, wykazał nadpłatę 1 284 zł, a zwrotu do dziś nie dostał (ustawowy termin to 45 dni). Poproś o informację o stanie sprawy i termin zwrotu, podaj numer konta w formie placeholdera. Ton urzędowy, ale nie archaiczny. 120-200 słów, z tematem wiadomości.",
   "odpowiedz": "**Temat:** Wniosek o informację w sprawie zwrotu nadpłaty podatku PIT-37 za 2025 r. – Jan Kowalski\n\nSzanowni Państwo,\n\nzwracam się z prośbą o udzielenie informacji na temat statusu zwrotu nadpłaty podatku dochodowego wynikającej z mojego zeznania rocznego za 2025 rok. \n\nW dniu 14 lutego 2026 r. złożyłem drogą elektroniczną, za pośrednictwem serwisu e-Urząd Skarbowy, deklarację PIT-37. Dokument został poprawnie przyjęty przez system, co potwierdza Urzędowe Poświadczenie Odbioru o numerze [TUTAJ WPISZ NUMER UPO]. W zeznaniu wykazałem nadpłatę podatku w kwocie 1 284 zł. \n\nZgodnie z obowiązującymi przepisami, przy rozliczeniu elektronicznym ustawowy termin zwrotu nadpłaty wynosi 45 dni od dnia złożenia deklaracji. Termin ten już upłynął, a środki do dnia dzisiejszego nie wpłynęły na zgłoszony przeze mnie rachunek bankowy o numerze: [TUTAJ WPISZ NUMER KONTA BANKOWEGO]. \n\nW związku z powyższym bardzo proszę o weryfikację mojej sprawy i informację, na jakim etapie znajduje się realizacja przelewu oraz kiedy mogę spodziewać się zwrotu należnej kwoty. Jeśli z Państwa strony wymagane są jakiekolwiek dodatkowe wyjaśnienia, pozostaję do dyspozycji.\n\nMoje dane identyfikacyjne:\nImię i nazwisko: Jan Kowalski\nPESEL: [TUTAJ WPISZ PESEL]\nAdres zamieszkania: [TUTAJ WPISZ ADRES]\nTelefon kontaktowy: [TUTAJ WPISZ NUMER TELEFONU]\n\nZ poważaniem,\nJan Kowalski",
   "czas_s": 9.94,
   "usage": {
    "prompt_tokens": 180,
    "completion_tokens": 412,
    "total_tokens": 592,
    "cost": 0.00448,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.00448,
     "upstream_inference_prompt_cost": 0.00036,
     "upstream_inference_completions_cost": 0.00412
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.00448,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.00448,
   "slowa": 190,
   "reczna_ocena": {
    "bledy_jezykowe": 2,
    "uwagi": "190 słów (w limicie). 'deklarację PIT-37' (to zeznanie), pleonazm 'W dniu 14 lutego', przecinek po 'Z poważaniem'. Najbardziej szablonowy."
   },
   "tokeny": {
    "wej": 180,
    "wyj": 412,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P1_urzad",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Napisz e-mail do Urzędu Skarbowego w imieniu podatnika (Jan Kowalski). Sytuacja: złożył zeznanie PIT-37 za 2025 rok 14 lutego 2026 r. przez e-Urząd Skarbowy, wykazał nadpłatę 1 284 zł, a zwrotu do dziś nie dostał (ustawowy termin to 45 dni). Poproś o informację o stanie sprawy i termin zwrotu, podaj numer konta w formie placeholdera. Ton urzędowy, ale nie archaiczny. 120-200 słów, z tematem wiadomości.",
   "odpowiedz": "**Temat: Prośba o informację o statusie zwrotu nadpłaty PIT-37 za 2025 rok**\n\nSzanowni Państwo,\n\nzwracam się z prośbą o informację dotyczącą zwrotu nadpłaty podatku dochodowego wykazanej w moim zeznaniu PIT-37 za 2025 rok.\n\nZeznanie złożyłem 14 lutego 2026 r. za pośrednictwem e-Urzędu Skarbowego. Wykazana w nim nadpłata wynosi 1 284 zł. Do dnia wysłania niniejszej wiadomości nie otrzymałem zwrotu, mimo upływu ustawowego terminu 45 dni przewidzianego dla zeznań składanych elektronicznie.\n\nProszę o sprawdzenie aktualnego stanu sprawy, wskazanie przyczyny opóźnienia oraz podanie przewidywanego terminu przekazania środków. Będę również wdzięczny za informację, czy do realizacji zwrotu konieczne jest złożenie przeze mnie dodatkowych dokumentów lub wyjaśnień.\n\nProszę o przekazanie zwrotu na następujący rachunek bankowy:  \n**[numer rachunku bankowego podatnika]**\n\nW razie potrzeby mogę przesłać urzędowe poświadczenie odbioru zeznania. Proszę o odpowiedź na adres e-mail, z którego wysłano tę wiadomość.\n\nZ poważaniem  \nJan Kowalski  \nPESEL: [numer PESEL]",
   "czas_s": 9.95,
   "usage": {
    "prompt_tokens": 152,
    "completion_tokens": 434,
    "total_tokens": 586,
    "cost": 0.004644,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.004644,
     "upstream_inference_prompt_cost": 0.000304,
     "upstream_inference_completions_cost": 0.00434
    },
    "completion_tokens_details": {
     "reasoning_tokens": 82,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.004644,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.004644,
   "slowa": 142,
   "reczna_ocena": {
    "bledy_jezykowe": 0,
    "uwagi": "Poprawny, bez ryzykownych dopowiedzeń prawnych; nie podaje podstawy prawnej ani daty upływu terminu, więc nie ma w czym się pomylić."
   },
   "tokeny": {
    "wej": 152,
    "wyj": 434,
    "rozumowanie": 82
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P2_streszczenie",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Streść poniższy tekst po polsku w maksymalnie 70 słowach, jednym akapitem. Zachowaj najważniejsze liczby. Nie dodawaj informacji, których nie ma w tekście.\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "Rada Miasta Brzeziny Górne przyjęła program „Rower za rogiem”: od 1 marca 2027 r. ruszy 14 stacji ze 160 rowerami, w tym 40 elektrycznymi. Pierwsze 20 minut będzie bezpłatne, potem 3 zł za godzinę (elektryczny 5 zł). Koszt to 2,1 mln zł na trzy lata, w połowie z dotacji wojewódzkiej. Opozycja była przeciw, wskazując na brak dróg rowerowych (11 km, do 2028 r. planowane 26 km). Osiedle Zatorze nie dostanie stacji.",
   "czas_s": 3.61,
   "usage": {
    "prompt_tokens": 744,
    "completion_tokens": 231,
    "total_tokens": 975,
    "cost": 0.015192,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.015192,
     "upstream_inference_prompt_cost": 0.005952,
     "upstream_inference_completions_cost": 0.00924
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.015192,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.015192,
   "slowa": 71,
   "reczna_ocena": {
    "bledy_jezykowe": 0,
    "uwagi": "71 słów przy limicie 70 (przekroczenie o 1). Liczby wierne. 'brak dróg rowerowych' to uproszczenie (w tekście: niedobór)."
   },
   "tokeny": {
    "wej": 744,
    "wyj": 231,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P1_urzad",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Napisz e-mail do Urzędu Skarbowego w imieniu podatnika (Jan Kowalski). Sytuacja: złożył zeznanie PIT-37 za 2025 rok 14 lutego 2026 r. przez e-Urząd Skarbowy, wykazał nadpłatę 1 284 zł, a zwrotu do dziś nie dostał (ustawowy termin to 45 dni). Poproś o informację o stanie sprawy i termin zwrotu, podaj numer konta w formie placeholdera. Ton urzędowy, ale nie archaiczny. 120-200 słów, z tematem wiadomości.",
   "odpowiedz": "**Temat:** Zapytanie o zwrot nadpłaty z zeznania PIT-37 za 2025 r. – Jan Kowalski, PESEL [numer PESEL]\n\nSzanowni Państwo,\n\n14 lutego 2026 r. złożyłem za pośrednictwem e-Urzędu Skarbowego zeznanie PIT-37 za 2025 rok, w którym wykazałem nadpłatę podatku w wysokości 1 284 zł. Ustawowy termin zwrotu nadpłaty z zeznania złożonego elektronicznie wynosi 45 dni, a więc upłynął 31 marca 2026 r. Do dnia dzisiejszego środki nie wpłynęły na mój rachunek bankowy.\n\nW związku z tym uprzejmie proszę o informację o aktualnym stanie sprawy, w tym o ewentualnych przyczynach opóźnienia, oraz o wskazanie przewidywanego terminu zwrotu nadpłaty. Jeżeli do rozpatrzenia sprawy potrzebne są dodatkowe dokumenty lub wyjaśnienia, proszę o informację. Dostarczę je niezwłocznie.\n\nZwrot proszę przekazać na mój rachunek bankowy o numerze: [numer rachunku bankowego].\n\nNumer referencyjny UPO złożonego zeznania: [numer UPO].\n\nZ poważaniem\nJan Kowalski\nPESEL: [numer PESEL]\nAdres: [adres zamieszkania]\nTel.: [numer telefonu]",
   "czas_s": 16.22,
   "usage": {
    "prompt_tokens": 227,
    "completion_tokens": 1662,
    "total_tokens": 1889,
    "cost": 0.034148,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.034148,
     "upstream_inference_prompt_cost": 0.000908,
     "upstream_inference_completions_cost": 0.03324
    },
    "completion_tokens_details": {
     "reasoning_tokens": 1120,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.034148,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.034148,
   "slowa": 143,
   "reczna_ocena": {
    "bledy_jezykowe": 0,
    "uwagi": "Językowo czysty. Wpadka merytoryczna: sam wylicza, że termin 'upłynął 31 marca 2026 r.', a zeznanie złożone przed 15 lutego uznaje się za złożone 15 lutego (art. 45 ust. 1 ustawy o PIT), więc termin mija 1 kwietnia. Do tego 1662 tokeny wyjścia na mail 143 słów (najwięcej myślenia)."
   },
   "tokeny": {
    "wej": 227,
    "wyj": 1662,
    "rozumowanie": 1120
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P2_streszczenie",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Streść poniższy tekst po polsku w maksymalnie 70 słowach, jednym akapitem. Zachowaj najważniejsze liczby. Nie dodawaj informacji, których nie ma w tekście.\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "Od 1 marca 2027 roku w Brzezinach Górnych ruszy program „Rower za rogiem”, obejmujący 14 stacji oraz 160 rowerów (w tym 40 elektrycznych). Po 20 darmowych minutach godzina jazdy wyniesie 3 lub 5 zł. Trzyletni koszt to 2,1 mln zł, w połowie pokryty z dotacji. Opozycja wskazuje na zaledwie 11 km ścieżek rowerowych, lecz do 2028 roku ma ich być 26 km. Z systemu chce korzystać 38% dorosłych mieszkańców, a pominiętemu osiedlu Zatorze obiecano rozważenie rozbudowy sieci po pierwszym sezonie.",
   "czas_s": 6.3,
   "usage": {
    "prompt_tokens": 451,
    "completion_tokens": 170,
    "total_tokens": 621,
    "cost": 0.002602,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002602,
     "upstream_inference_prompt_cost": 0.000902,
     "upstream_inference_completions_cost": 0.0017
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002602,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002602,
   "slowa": 80,
   "reczna_ocena": {
    "bledy_jezykowe": 0,
    "uwagi": "80 słów przy limicie 70 (przekroczenie o 10). Dodaje ocenę 'zaledwie 11 km', której nie ma w tekście."
   },
   "tokeny": {
    "wej": 451,
    "wyj": 170,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P2_streszczenie",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Streść poniższy tekst po polsku w maksymalnie 70 słowach, jednym akapitem. Zachowaj najważniejsze liczby. Nie dodawaj informacji, których nie ma w tekście.\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "Od 1 marca 2027 w Brzezinach Górnych ma działać 14 stacji ze 160 rowerami, w tym 40 elektrycznymi. Pierwsze 20 minut będzie bezpłatne, kolejna rozpoczęta godzina: 3 zł, elektrykiem: 5 zł. Trzyletni koszt wyniesie 2,1 mln zł; połowę pokryje dotacja. Sieć dróg rowerowych ma wzrosnąć z 11 do 26 km do 2028 roku; 6 km powstanie przed uruchomieniem wypożyczalni. Korzystaniem zainteresowanych jest 38% dorosłych mieszkańców.",
   "czas_s": 7.63,
   "usage": {
    "prompt_tokens": 481,
    "completion_tokens": 478,
    "total_tokens": 959,
    "cost": 0.005742,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.005742,
     "upstream_inference_prompt_cost": 0.000962,
     "upstream_inference_completions_cost": 0.00478
    },
    "completion_tokens_details": {
     "reasoning_tokens": 322,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.005742,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.005742,
   "slowa": 65,
   "reczna_ocena": {
    "bledy_jezykowe": 1,
    "uwagi": "65 słów. Szyk 'Korzystaniem zainteresowanych jest 38% dorosłych mieszkańców' i potoczne 'elektrykiem'."
   },
   "tokeny": {
    "wej": 481,
    "wyj": 478,
    "rozumowanie": 322
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P3_marketing",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Napisz tekst na stronę główną (120-160 słów) dla „Ziarnko” - małej palarni kawy z Lublina, która wysyła świeżo paloną kawę w subskrypcji co 2 tygodnie (od 49 zł za 250 g, dostawa gratis, można wstrzymać w każdej chwili). Ma brzmieć jak człowiek, nie jak reklama z generatora: bez frazesów typu „przenieś się w świat smaku”, „odkryj magię”, bez wykrzykników. Dodaj nagłówek i jedno wezwanie do działania na końcu.",
   "odpowiedz": "# Kawa palona w Lublinie, u Ciebie co dwa tygodnie\n\nJesteśmy małą palarnią. Na tyle małą, że każdą partię palimy sami i sami pakujemy ją do paczek. Nie mamy magazynu z zapasem na pół roku, bo kawa smakuje najlepiej w pierwszych tygodniach po paleniu, a potem z każdym miesiącem traci aromat. Dlatego wysyłamy ją świeżą, zaraz po wypaleniu.\n\nSubskrypcja działa prosto. Co dwa tygodnie dostajesz paczkę 250 g, czyli mniej więcej tyle, ile schodzi przy jednej, dwóch kawach dziennie. Ceny zaczynają się od 49 zł, a dostawa jest wliczona, więc przy kasie nic nie dochodzi.\n\nNie wiążemy Cię umową. Jeśli wyjeżdżasz na urlop albo po prostu uzbierał Ci się zapas, wstrzymujesz wysyłkę w dowolnym momencie i wracasz, kiedy chcesz. Bez tłumaczenia się i bez dzwonienia na infolinię, której zresztą nie mamy.\n\nNa każdej paczce znajdziesz datę palenia. Wolimy, żebyś to widział, niż wierzył nam na słowo.\n\n**[Zamów pierwszą paczkę]**",
   "czas_s": 8.06,
   "usage": {
    "prompt_tokens": 215,
    "completion_tokens": 494,
    "total_tokens": 709,
    "cost": 0.02148,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.02148,
     "upstream_inference_prompt_cost": 0.00172,
     "upstream_inference_completions_cost": 0.01976
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.02148,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.02148,
   "slowa": 148,
   "reczna_ocena": {
    "bledy_jezykowe": 0,
    "uwagi": "Najbardziej ludzki ton (infolinia, której nie mamy). Nie pada nazwa Ziarnko. Dopisuje datę palenia na paczce i brak infolinii - niepodane w poleceniu."
   },
   "tokeny": {
    "wej": 215,
    "wyj": 494,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P3_marketing",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Napisz tekst na stronę główną (120-160 słów) dla „Ziarnko” - małej palarni kawy z Lublina, która wysyła świeżo paloną kawę w subskrypcji co 2 tygodnie (od 49 zł za 250 g, dostawa gratis, można wstrzymać w każdej chwili). Ma brzmieć jak człowiek, nie jak reklama z generatora: bez frazesów typu „przenieś się w świat smaku”, „odkryj magię”, bez wykrzykników. Dodaj nagłówek i jedno wezwanie do działania na końcu.",
   "odpowiedz": "**Kawa, która jeszcze wczoraj była w piecu**\n\nCześć, tu Ziarnko. Jesteśmy małą palarnią z Lublina. Nie będziemy Ci pisać o egzotycznych zboczach wulkanów ani magii poranka, bo oboje wiemy, że o siódmej rano po prostu potrzebujesz dobrego naparu. My dbamy o to, żeby zawsze był pod ręką. \n\nWypalamy ziarna w niewielkich partiach, dzięki czemu dokładnie wiemy, co trafia do paczki. Działamy w prostym modelu subskrypcji: co dwa tygodnie wysyłamy pod Twoje drzwi świeżo wypaloną kawę. Taka paczka robi w młynku i kubku ogromną różnicę w porównaniu z tą, która spędziła pół roku na sklepowej półce. \n\nZaczynamy od 49 zł za 250 gramów. Za dostawę nic nie dopłacasz. Nie wiążemy Cię też żadnymi umowami. Wyjeżdżasz na urlop albo masz jeszcze zapas w szafce? Wstrzymujesz lub anulujesz subskrypcję jednym kliknięciem na swoim koncie. Zero zobowiązań, po prostu uczciwa kawa na Twoich zasadach.\n\n[Wybierz swoje ziarna i zamów pierwszą paczkę]",
   "czas_s": 8.91,
   "usage": {
    "prompt_tokens": 174,
    "completion_tokens": 298,
    "total_tokens": 472,
    "cost": 0.003328,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.003328,
     "upstream_inference_prompt_cost": 0.000348,
     "upstream_inference_completions_cost": 0.00298
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.003328,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.003328,
   "slowa": 147,
   "reczna_ocena": {
    "bledy_jezykowe": 2,
    "uwagi": "Kalka 'robi ogromną różnicę', 'oboje wiemy' (forma dla pary kobieta-mężczyzna), dopisane anulowanie jednym kliknięciem i wybór ziaren. Ostentacyjnie zaprzecza frazesom, co samo jest frazesem."
   },
   "tokeny": {
    "wej": 174,
    "wyj": 298,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P3_marketing",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Napisz tekst na stronę główną (120-160 słów) dla „Ziarnko” - małej palarni kawy z Lublina, która wysyła świeżo paloną kawę w subskrypcji co 2 tygodnie (od 49 zł za 250 g, dostawa gratis, można wstrzymać w każdej chwili). Ma brzmieć jak człowiek, nie jak reklama z generatora: bez frazesów typu „przenieś się w świat smaku”, „odkryj magię”, bez wykrzykników. Dodaj nagłówek i jedno wezwanie do działania na końcu.",
   "odpowiedz": "# Dobra kawa. Co dwa tygodnie pod Twoimi drzwiami.\n\nPoranek bywa wystarczająco skomplikowany. Niech chociaż kawa będzie pod ręką.\n\nZiarnko to mała palarnia z Lublina. Palimy kawę i wysyłamy ją w subskrypcji co dwa tygodnie, żeby nie trzeba było dopisywać kolejnej rzeczy do listy zakupów. Dostajesz świeżo palone ziarna, a my zajmujemy się kolejną przesyłką.\n\nSubskrypcja zaczyna się od 49 zł za 250 g. Dostawa jest gratis — nie doliczamy jej na ostatnim kroku zamówienia. A jeśli wyjeżdżasz, pijesz ostatnio mniej kawy albo w szafce zrobił się zapas, możesz wstrzymać subskrypcję w każdej chwili. To ma być wygoda, nie kolejne zobowiązanie, o którym trzeba pamiętać.\n\nKawę pijemy w różnym tempie i na różne sposoby. Czasem spokojnie przy śniadaniu, czasem kilka łyków między jednym zadaniem a drugim. Ziarnko jest po to, żeby w tych zwykłych chwilach nie zabrakło świeżej kawy.\n\n**Wybierz subskrypcję Ziarnko**",
   "czas_s": 11.2,
   "usage": {
    "prompt_tokens": 156,
    "completion_tokens": 353,
    "total_tokens": 509,
    "cost": 0.003842,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.003842,
     "upstream_inference_prompt_cost": 0.000312,
     "upstream_inference_completions_cost": 0.00353
    },
    "completion_tokens_details": {
     "reasoning_tokens": 51,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.003842,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.003842,
   "slowa": 140,
   "reczna_ocena": {
    "bledy_jezykowe": 1,
    "uwagi": "Jedyny z nazwą marki i bez dopisanych obietnic. Używa długiej pauzy (—) zamiast dywizu - typograficznie poprawne, ale to znak, którego nie chcemy."
   },
   "tokeny": {
    "wej": 156,
    "wyj": 353,
    "rozumowanie": 51
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P2_streszczenie",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Streść poniższy tekst po polsku w maksymalnie 70 słowach, jednym akapitem. Zachowaj najważniejsze liczby. Nie dodawaj informacji, których nie ma w tekście.\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "Rada Miasta Brzeziny Górne przyjęła program „Rower za rogiem”: od 1 marca 2027 r. ma działać 14 stacji i 160 rowerów, w tym 40 elektrycznych. Pierwsze 20 minut bezpłatne, potem 3 zł (elektryczny 5 zł) za rozpoczętą godzinę. Program kosztuje 2,1 mln zł w trzy lata; połowę pokryje dotacja wojewódzka. Opozycja głosowała przeciw, wskazując na niedobór dróg rowerowych: 11 km, a do 2028 r. ma być 26 km.",
   "czas_s": 13.2,
   "usage": {
    "prompt_tokens": 744,
    "completion_tokens": 1626,
    "total_tokens": 2370,
    "cost": 0.035496,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.035496,
     "upstream_inference_prompt_cost": 0.002976,
     "upstream_inference_completions_cost": 0.03252
    },
    "completion_tokens_details": {
     "reasoning_tokens": 1403,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.035496,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.035496,
   "slowa": 68,
   "reczna_ocena": {
    "bledy_jezykowe": 0,
    "uwagi": "68 słów, wszystkie kluczowe liczby, najwierniejsze streszczenie."
   },
   "tokeny": {
    "wej": 744,
    "wyj": 1626,
    "rozumowanie": 1403
   }
  },
  {
   "kategoria": "polski",
   "zadanie": "P3_marketing",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Napisz tekst na stronę główną (120-160 słów) dla „Ziarnko” - małej palarni kawy z Lublina, która wysyła świeżo paloną kawę w subskrypcji co 2 tygodnie (od 49 zł za 250 g, dostawa gratis, można wstrzymać w każdej chwili). Ma brzmieć jak człowiek, nie jak reklama z generatora: bez frazesów typu „przenieś się w świat smaku”, „odkryj magię”, bez wykrzykników. Dodaj nagłówek i jedno wezwanie do działania na końcu.",
   "odpowiedz": "# Kawa z Lublina, palona w zeszłym tygodniu\n\nJesteśmy małą palarnią z Lublina. Palimy kawę w niewielkich partiach, kilka razy w tygodniu, więc to, co do Ciebie trafia, nie leżało miesiącami w magazynie. Na opakowaniu zawsze znajdziesz datę palenia.\n\nDziała to prosto: co dwa tygodnie wysyłamy Ci świeżą paczkę. Subskrypcja zaczyna się od 49 zł za 250 g, a dostawę bierzemy na siebie. Jeśli wyjeżdżasz na urlop albo masz jeszcze zapas, możesz wstrzymać wysyłki w każdej chwili, bez telefonów, maili i tłumaczenia się.\n\nNie obiecujemy, że każda kawa Ci podejdzie. Dlatego do każdej paczki dokładamy kilka zdań o tym, skąd pochodzi ziarno, jak smakuje i jak my ją parzymy. Nie musisz znać się na kawie, żeby z tego korzystać. Jeśli coś nie zagra, daj znać, a następnym razem dobierzemy coś innego.\n\n**[Zamów pierwszą paczkę]**",
   "czas_s": 17.26,
   "usage": {
    "prompt_tokens": 215,
    "completion_tokens": 1867,
    "total_tokens": 2082,
    "cost": 0.0382,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.0382,
     "upstream_inference_prompt_cost": 0.00086,
     "upstream_inference_completions_cost": 0.03734
    },
    "completion_tokens_details": {
     "reasoning_tokens": 1428,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.0382,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.0382,
   "slowa": 133,
   "reczna_ocena": {
    "bledy_jezykowe": 0,
    "uwagi": "Naturalny, ale bez nazwy Ziarnko i z wymyśloną ofertą (opis ziarna w paczce, dobór innej kawy)."
   },
   "tokeny": {
    "wej": 215,
    "wyj": 1867,
    "rozumowanie": 1428
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W1_wykres",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Na obrazie jest wykres. Odpowiedz na pytania, patrząc tylko na obraz.\n1) W którym miesiącu sklep „Północ” miał najniższą sprzedaż?\n2) Ile wynosi łączna sprzedaż sklepu „Południe” w kwietniu, maju i czerwcu (suma trzech wartości)?\n3) W ilu miesiącach sprzedaż „Południe” była wyższa niż „Północ”?\nOdpowiedz wyłącznie JSON: {\"q1\": \"<nazwa miesiąca po polsku>\", \"q2\": <liczba>, \"q3\": <liczba>}",
   "odpowiedz": "{\"q1\": \"maj\", \"q2\": 151, \"q3\": 5}",
   "czas_s": 3.47,
   "usage": {
    "prompt_tokens": 1069,
    "completion_tokens": 25,
    "total_tokens": 1094,
    "cost": 0.009552,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.009552,
     "upstream_inference_prompt_cost": 0.008552,
     "upstream_inference_completions_cost": 0.001
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.009552,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.009552,
   "ocena": {
    "ok": 3,
    "total": 3,
    "czysty_json": true,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true
    },
    "odpowiedz": {
     "q1": "maj",
     "q2": 151,
     "q3": 5
    }
   },
   "obraz": "obrazy/w1_wykres.png",
   "tokeny": {
    "wej": 1069,
    "wyj": 25,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W1_wykres",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Na obrazie jest wykres. Odpowiedz na pytania, patrząc tylko na obraz.\n1) W którym miesiącu sklep „Północ” miał najniższą sprzedaż?\n2) Ile wynosi łączna sprzedaż sklepu „Południe” w kwietniu, maju i czerwcu (suma trzech wartości)?\n3) W ilu miesiącach sprzedaż „Południe” była wyższa niż „Północ”?\nOdpowiedz wyłącznie JSON: {\"q1\": \"<nazwa miesiąca po polsku>\", \"q2\": <liczba>, \"q3\": <liczba>}",
   "odpowiedz": "{\"q1\": \"maj\", \"q2\": 151, \"q3\": 5}",
   "czas_s": 4.32,
   "usage": {
    "prompt_tokens": 1069,
    "completion_tokens": 118,
    "total_tokens": 1187,
    "cost": 0.006636,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.006636,
     "upstream_inference_prompt_cost": 0.004276,
     "upstream_inference_completions_cost": 0.00236
    },
    "completion_tokens_details": {
     "reasoning_tokens": 93,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.006636,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.006636,
   "ocena": {
    "ok": 3,
    "total": 3,
    "czysty_json": true,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true
    },
    "odpowiedz": {
     "q1": "maj",
     "q2": 151,
     "q3": 5
    }
   },
   "obraz": "obrazy/w1_wykres.png",
   "tokeny": {
    "wej": 1069,
    "wyj": 118,
    "rozumowanie": 93
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W1_wykres",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Na obrazie jest wykres. Odpowiedz na pytania, patrząc tylko na obraz.\n1) W którym miesiącu sklep „Północ” miał najniższą sprzedaż?\n2) Ile wynosi łączna sprzedaż sklepu „Południe” w kwietniu, maju i czerwcu (suma trzech wartości)?\n3) W ilu miesiącach sprzedaż „Południe” była wyższa niż „Północ”?\nOdpowiedz wyłącznie JSON: {\"q1\": \"<nazwa miesiąca po polsku>\", \"q2\": <liczba>, \"q3\": <liczba>}",
   "odpowiedz": "{\"q1\":\"maj\",\"q2\":151,\"q3\":5}",
   "czas_s": 4.72,
   "usage": {
    "prompt_tokens": 904,
    "completion_tokens": 59,
    "total_tokens": 963,
    "cost": 0.002398,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002398,
     "upstream_inference_prompt_cost": 0.001808,
     "upstream_inference_completions_cost": 0.00059
    },
    "completion_tokens_details": {
     "reasoning_tokens": 37,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002398,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002398,
   "ocena": {
    "ok": 3,
    "total": 3,
    "czysty_json": true,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true
    },
    "odpowiedz": {
     "q1": "maj",
     "q2": 151,
     "q3": 5
    }
   },
   "obraz": "obrazy/w1_wykres.png",
   "tokeny": {
    "wej": 904,
    "wyj": 59,
    "rozumowanie": 37
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W1_wykres",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Na obrazie jest wykres. Odpowiedz na pytania, patrząc tylko na obraz.\n1) W którym miesiącu sklep „Północ” miał najniższą sprzedaż?\n2) Ile wynosi łączna sprzedaż sklepu „Południe” w kwietniu, maju i czerwcu (suma trzech wartości)?\n3) W ilu miesiącach sprzedaż „Południe” była wyższa niż „Północ”?\nOdpowiedz wyłącznie JSON: {\"q1\": \"<nazwa miesiąca po polsku>\", \"q2\": <liczba>, \"q3\": <liczba>}",
   "odpowiedz": "```json\n{\"q1\": \"maj\", \"q2\": 151, \"q3\": 5}\n```",
   "czas_s": 5.0,
   "usage": {
    "prompt_tokens": 1262,
    "completion_tokens": 32,
    "total_tokens": 1294,
    "cost": 0.002844,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002844,
     "upstream_inference_prompt_cost": 0.002524,
     "upstream_inference_completions_cost": 0.00032
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002844,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002844,
   "ocena": {
    "ok": 3,
    "total": 3,
    "czysty_json": false,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true
    },
    "odpowiedz": {
     "q1": "maj",
     "q2": 151,
     "q3": 5
    }
   },
   "obraz": "obrazy/w1_wykres.png",
   "tokeny": {
    "wej": 1262,
    "wyj": 32,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W2_tabela",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Na obrazie jest zrzut tabeli z arkusza. Odpowiedz na pytania, patrząc tylko na obraz.\n1) Który produkt ma najwyższą wartość (ilość × cena netto)? Podaj jego kod (kolumna Kod).\n2) Ile wynosi suma kolumny „Ilość”?\n3) Ile produktów ma stawkę VAT 8%?\nOdpowiedz wyłącznie JSON: {\"q1\": \"<kod>\", \"q2\": <liczba>, \"q3\": <liczba>}",
   "odpowiedz": "Obliczenia wartości:\n- A-104: 14 × 18,90 = 264,60\n- A-221: 6 × 49,00 = 294,00\n- B-017: 25 × 11,40 = 285,00\n- B-052: 3 × 64,20 = 192,60\n- C-310: 2 × 150,00 = 300,00\n- C-311: 9 × 32,50 = 292,50\n- A-399: 4 × 89,99 = 359,96\n- B-120: 12 × 27,80 = 333,60\n- C-400: 18 × 16,00 = 288,00\n- A-512: 5 × 72,40 = 362,00\n\nSuma ilości: 14+6+25+3+2+9+4+12+18+5 = 98\n\nVAT 8%: C-310, C-311, C-400 → 3\n\n{\"q1\": \"A-512\", \"q2\": 98, \"q3\": 3}",
   "czas_s": 4.57,
   "usage": {
    "prompt_tokens": 997,
    "completion_tokens": 292,
    "total_tokens": 1289,
    "cost": 0.019656,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.019656,
     "upstream_inference_prompt_cost": 0.007976,
     "upstream_inference_completions_cost": 0.01168
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.019656,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.019656,
   "ocena": {
    "ok": 3,
    "total": 3,
    "czysty_json": false,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true
    },
    "odpowiedz": {
     "q1": "A-512",
     "q2": 98,
     "q3": 3
    }
   },
   "obraz": "obrazy/w2_tabela.png",
   "tokeny": {
    "wej": 997,
    "wyj": 292,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W2_tabela",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Na obrazie jest zrzut tabeli z arkusza. Odpowiedz na pytania, patrząc tylko na obraz.\n1) Który produkt ma najwyższą wartość (ilość × cena netto)? Podaj jego kod (kolumna Kod).\n2) Ile wynosi suma kolumny „Ilość”?\n3) Ile produktów ma stawkę VAT 8%?\nOdpowiedz wyłącznie JSON: {\"q1\": \"<kod>\", \"q2\": <liczba>, \"q3\": <liczba>}",
   "odpowiedz": "{\"q1\": \"A-512\", \"q2\": 98, \"q3\": 3}",
   "czas_s": 5.04,
   "usage": {
    "prompt_tokens": 997,
    "completion_tokens": 247,
    "total_tokens": 1244,
    "cost": 0.008928,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.008928,
     "upstream_inference_prompt_cost": 0.003988,
     "upstream_inference_completions_cost": 0.00494
    },
    "completion_tokens_details": {
     "reasoning_tokens": 221,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.008928,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.008928,
   "ocena": {
    "ok": 3,
    "total": 3,
    "czysty_json": true,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true
    },
    "odpowiedz": {
     "q1": "A-512",
     "q2": 98,
     "q3": 3
    }
   },
   "obraz": "obrazy/w2_tabela.png",
   "tokeny": {
    "wej": 997,
    "wyj": 247,
    "rozumowanie": 221
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W2_tabela",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Na obrazie jest zrzut tabeli z arkusza. Odpowiedz na pytania, patrząc tylko na obraz.\n1) Który produkt ma najwyższą wartość (ilość × cena netto)? Podaj jego kod (kolumna Kod).\n2) Ile wynosi suma kolumny „Ilość”?\n3) Ile produktów ma stawkę VAT 8%?\nOdpowiedz wyłącznie JSON: {\"q1\": \"<kod>\", \"q2\": <liczba>, \"q3\": <liczba>}",
   "odpowiedz": "```json\n{\"q1\": \"A-512\", \"q2\": 98, \"q3\": 3}\n```",
   "czas_s": 4.74,
   "usage": {
    "prompt_tokens": 1199,
    "completion_tokens": 35,
    "total_tokens": 1234,
    "cost": 0.002748,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002748,
     "upstream_inference_prompt_cost": 0.002398,
     "upstream_inference_completions_cost": 0.00035
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002748,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002748,
   "ocena": {
    "ok": 3,
    "total": 3,
    "czysty_json": false,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true
    },
    "odpowiedz": {
     "q1": "A-512",
     "q2": 98,
     "q3": 3
    }
   },
   "obraz": "obrazy/w2_tabela.png",
   "tokeny": {
    "wej": 1199,
    "wyj": 35,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W3_paragon",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Na zdjęciu jest paragon. Odpowiedz na pytania, patrząc tylko na obraz.\n1) Ile wynosi SUMA PLN do zapłaty?\n2) Ile kosztowała pozycja „MLEKO 3,2% 1L” w sumie (wartość pozycji)?\n3) Jaki jest NIP sprzedawcy (same cyfry, bez kresek)?\n4) Ile pozycji towarowych jest na paragonie?\nOdpowiedz wyłącznie JSON: {\"q1\": <liczba>, \"q2\": <liczba>, \"q3\": \"<cyfry>\", \"q4\": <liczba>}",
   "odpowiedz": "{\"q1\": 81.03, \"q2\": 7.58, \"q3\": \"9462713805\", \"q4\": 7}",
   "czas_s": 4.51,
   "usage": {
    "prompt_tokens": 1718,
    "completion_tokens": 38,
    "total_tokens": 1756,
    "cost": 0.015264,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.015264,
     "upstream_inference_prompt_cost": 0.013744,
     "upstream_inference_completions_cost": 0.00152
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.015264,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.015264,
   "ocena": {
    "ok": 4,
    "total": 4,
    "czysty_json": true,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true,
     "q4": true
    },
    "odpowiedz": {
     "q1": 81.03,
     "q2": 7.58,
     "q3": "9462713805",
     "q4": 7
    }
   },
   "obraz": "obrazy/w3_paragon.jpg",
   "tokeny": {
    "wej": 1718,
    "wyj": 38,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W3_paragon",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Na zdjęciu jest paragon. Odpowiedz na pytania, patrząc tylko na obraz.\n1) Ile wynosi SUMA PLN do zapłaty?\n2) Ile kosztowała pozycja „MLEKO 3,2% 1L” w sumie (wartość pozycji)?\n3) Jaki jest NIP sprzedawcy (same cyfry, bez kresek)?\n4) Ile pozycji towarowych jest na paragonie?\nOdpowiedz wyłącznie JSON: {\"q1\": <liczba>, \"q2\": <liczba>, \"q3\": \"<cyfry>\", \"q4\": <liczba>}",
   "odpowiedz": "{\"q1\": 81.03, \"q2\": 7.58, \"q3\": \"9462713805\", \"q4\": 7}",
   "czas_s": 3.69,
   "usage": {
    "prompt_tokens": 1718,
    "completion_tokens": 160,
    "total_tokens": 1878,
    "cost": 0.010072,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.010072,
     "upstream_inference_prompt_cost": 0.006872,
     "upstream_inference_completions_cost": 0.0032
    },
    "completion_tokens_details": {
     "reasoning_tokens": 122,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.010072,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.010072,
   "ocena": {
    "ok": 4,
    "total": 4,
    "czysty_json": true,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true,
     "q4": true
    },
    "odpowiedz": {
     "q1": 81.03,
     "q2": 7.58,
     "q3": "9462713805",
     "q4": 7
    }
   },
   "obraz": "obrazy/w3_paragon.jpg",
   "tokeny": {
    "wej": 1718,
    "wyj": 160,
    "rozumowanie": 122
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W2_tabela",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Na obrazie jest zrzut tabeli z arkusza. Odpowiedz na pytania, patrząc tylko na obraz.\n1) Który produkt ma najwyższą wartość (ilość × cena netto)? Podaj jego kod (kolumna Kod).\n2) Ile wynosi suma kolumny „Ilość”?\n3) Ile produktów ma stawkę VAT 8%?\nOdpowiedz wyłącznie JSON: {\"q1\": \"<kod>\", \"q2\": <liczba>, \"q3\": <liczba>}",
   "odpowiedz": "{\"q1\":\"A-512\",\"q2\":98,\"q3\":3}",
   "czas_s": 8.94,
   "usage": {
    "prompt_tokens": 882,
    "completion_tokens": 89,
    "total_tokens": 971,
    "cost": 0.002654,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002654,
     "upstream_inference_prompt_cost": 0.001764,
     "upstream_inference_completions_cost": 0.00089
    },
    "completion_tokens_details": {
     "reasoning_tokens": 65,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002654,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002654,
   "ocena": {
    "ok": 3,
    "total": 3,
    "czysty_json": true,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true
    },
    "odpowiedz": {
     "q1": "A-512",
     "q2": 98,
     "q3": 3
    }
   },
   "obraz": "obrazy/w2_tabela.png",
   "tokeny": {
    "wej": 882,
    "wyj": 89,
    "rozumowanie": 65
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W3_paragon",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Na zdjęciu jest paragon. Odpowiedz na pytania, patrząc tylko na obraz.\n1) Ile wynosi SUMA PLN do zapłaty?\n2) Ile kosztowała pozycja „MLEKO 3,2% 1L” w sumie (wartość pozycji)?\n3) Jaki jest NIP sprzedawcy (same cyfry, bez kresek)?\n4) Ile pozycji towarowych jest na paragonie?\nOdpowiedz wyłącznie JSON: {\"q1\": <liczba>, \"q2\": <liczba>, \"q3\": \"<cyfry>\", \"q4\": <liczba>}",
   "odpowiedz": "```json\n{\"q1\": 81.03, \"q2\": 7.58, \"q3\": \"9462713805\", \"q4\": 7}\n```",
   "czas_s": 4.46,
   "usage": {
    "prompt_tokens": 1249,
    "completion_tokens": 53,
    "total_tokens": 1302,
    "cost": 0.003028,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.003028,
     "upstream_inference_prompt_cost": 0.002498,
     "upstream_inference_completions_cost": 0.00053
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.003028,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.003028,
   "ocena": {
    "ok": 4,
    "total": 4,
    "czysty_json": false,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true,
     "q4": true
    },
    "odpowiedz": {
     "q1": 81.03,
     "q2": 7.58,
     "q3": "9462713805",
     "q4": 7
    }
   },
   "obraz": "obrazy/w3_paragon.jpg",
   "tokeny": {
    "wej": 1249,
    "wyj": 53,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "wizja",
   "zadanie": "W3_paragon",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Na zdjęciu jest paragon. Odpowiedz na pytania, patrząc tylko na obraz.\n1) Ile wynosi SUMA PLN do zapłaty?\n2) Ile kosztowała pozycja „MLEKO 3,2% 1L” w sumie (wartość pozycji)?\n3) Jaki jest NIP sprzedawcy (same cyfry, bez kresek)?\n4) Ile pozycji towarowych jest na paragonie?\nOdpowiedz wyłącznie JSON: {\"q1\": <liczba>, \"q2\": <liczba>, \"q3\": \"<cyfry>\", \"q4\": <liczba>}",
   "odpowiedz": "{\"q1\":81.03,\"q2\":7.58,\"q3\":\"9462713805\",\"q4\":7}",
   "czas_s": 2.68,
   "usage": {
    "prompt_tokens": 1507,
    "completion_tokens": 32,
    "total_tokens": 1539,
    "cost": 0.004086,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 1504,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.004086,
     "upstream_inference_prompt_cost": 0.003766,
     "upstream_inference_completions_cost": 0.00032
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.004086,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.003334,
   "ocena": {
    "ok": 4,
    "total": 4,
    "czysty_json": true,
    "szczegoly": {
     "q1": true,
     "q2": true,
     "q3": true,
     "q4": true
    },
    "odpowiedz": {
     "q1": 81.03,
     "q2": 7.58,
     "q3": "9462713805",
     "q4": 7
    }
   },
   "obraz": "obrazy/w3_paragon.jpg",
   "tokeny": {
    "wej": 1507,
    "wyj": 32,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S1_esej#1",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Napisz po polsku tekst na około 500 słów o tym, jak działa kompostownik w ogrodzie. Bez wstępów i podsumowań typu „podsumowując”.",
   "odpowiedz": "Kompostownik to miejsce, w którym resztki roślinne i kuchenne zamieniają się w żyzną próchnicę dzięki pracy miliardów mikroorganizmów. Bakterie, grzyby i promieniowce rozkładają materię organiczną, a w kolejnych etapach dołączają do nich dżdżownice, stonogi, skoczogonki i inne drobne zwierzęta glebowe. Aby cały proces przebiegał sprawnie, organizmy te potrzebują czterech rzeczy: pożywienia bogatego w węgiel, pożywienia bogatego w azot, wody oraz tlenu.\n\nMateriały węglowe, nazywane często „brązowymi”, to suche liście, słoma, rozdrobnione gałązki, trociny, tektura i papier bez nadruku. Dostarczają one energii mikroorganizmom i nadają pryzmie luźną strukturę. Materiały azotowe, czyli „zielone”, to skoszona trawa, obierki z warzyw i owoców, fusy z kawy, chwasty bez nasion oraz świeże resztki roślin. Azot pozwala bakteriom budować białka i szybko się namnażać. Najlepsze efekty daje mieszanie obu grup w proporcji objętościowej mniej więcej dwóch–trzech części brązowych na jedną zieloną. Nadmiar zielonych sprawia, że kompost gnije i wydziela zapach amoniaku, a nadmiar brązowych spowalnia rozkład nawet o wiele miesięcy.\n\nW dobrze ułożonej pryzmie proces przebiega w kilku fazach. Na początku bakterie mezofilne rozkładają najłatwiej dostępne cukry i białka, a wytwarzane przez nie ciepło podnosi temperaturę wnętrza. Po kilku dniach władzę przejmują organizmy termofilne, a temperatura może sięgnąć 50–65 stopni Celsjusza. W tej gorącej fazie giną nasiona wielu chwastów i znaczna część patogenów, a rozkładowi ulegają trudniejsze związki, takie jak celuloza. Gdy pożywienie się wyczerpuje, pryzma stygnie i zaczyna się faza dojrzewania. Wtedy do pracy przystępują grzyby rozkładające ligninę oraz dżdżownice, które przepuszczają materię przez swój przewód pokarmowy i wzbogacają ją w związki łatwo przyswajalne dla roślin.\n\nWilgotność powinna przypominać wyciśniętą gąbkę: garść materiału ściśnięta w dłoni ma być wilgotna, ale nie może z niej kapać woda. Zbyt sucha pryzma zamiera, dlatego w upalne lato warto ją podlewać. Zbyt mokra traci powietrze i zaczynają w niej dominować bakterie beztlenowe, odpowiedzialne za nieprzyjemny zapach zgnilizny. Dostęp tlenu zapewnia przekładanie kompostu widłami co kilka tygodni oraz dodawanie grubszych elementów, na przykład pociętych gałęzi, które tworzą kanały powietrzne. Z tego samego powodu kompostownik powinien mieć szczeliny w ściankach i stać bezpośrednio na ziemi, a nie na betonie, dzięki czemu nadmiar wody swobodnie odpływa, a organizmy glebowe mogą wchodzić do środka od dołu.\n\nNajlepszym miejscem jest zacieniony, osłonięty od wiatru kąt ogrodu. Na spód układa się warstwę gałązek jako drenaż, a następnie naprzemiennie materiały brązowe i zielone, najlepiej rozdrobnione, ponieważ mniejsze kawałki rozkładają się znacznie szybciej. Do kompostownika nie należy wrzucać mięsa, kości, nabiału, tłuszczów, odchodów psów i kotów ani roślin porażonych chorobami, ponieważ przyciągają gryzonie lub przenoszą patogeny. Unikać trzeba także chwastów z dojrzałymi nasionami oraz kłączy perzu, jeśli pryzma nie nagrzewa się wystarczająco mocno.\n\nDojrzały kompost powstaje zwykle po sześciu do dwunastu miesiącach, a przy regularnym przerzucaniu nawet po trzech–czterech. Poznaje się go po ciemnobrązowej barwie, gruzełkowatej strukturze i zapachu leśnej ściółki; pierwotne składniki nie powinny być już rozpoznawalne. Taki materiał rozkłada się cienką warstwą na grządkach, miesza z ziemią przy sadzeniu albo używa jako ściółki pod krzewami. Poprawia on strukturę gleby, zwiększa jej zdolność zatrzymywania wody i stopniowo uwalnia azot, fosfor, potas oraz mikroelementy.",
   "czas_s": 18.764,
   "usage": {
    "prompt_tokens": 70,
    "completion_tokens": 1753,
    "total_tokens": 1823,
    "cost": 0.07068,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.07068,
     "upstream_inference_prompt_cost": 0.00056,
     "upstream_inference_completions_cost": 0.07012
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.07068,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.07068,
   "ttft_s": 1.426,
   "ttft_dowolny_token_s": 1.426,
   "gen_s": 17.339,
   "tok_s": 101.1,
   "slowa": 497,
   "slowa_na_s_calkowity_czas": 26.5,
   "tokeny": {
    "wej": 70,
    "wyj": 1753,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S2_kod#1",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Napisz w Pythonie kompletną implementację kolejki priorytetowej opartej na kopcu binarnym (klasa z metodami push, pop, peek, __len__, decrease_key) z docstringami i komentarzami. Tylko kod.",
   "odpowiedz": "```python\n\"\"\"Kolejka priorytetowa oparta na kopcu binarnym (min-kopiec).\"\"\"\n\nfrom typing import Any, Dict, Hashable, List, Tuple\n\n\nclass PriorityQueue:\n    \"\"\"Kolejka priorytetowa typu min (najmniejszy priorytet wychodzi pierwszy).\n\n    Implementacja wykorzystuje kopiec binarny przechowywany w liście oraz\n    słownik pozycji elementów, dzięki czemu operacja ``decrease_key`` działa\n    w czasie O(log n).\n\n    Elementy muszą być haszowalne i unikalne w obrębie kolejki.\n    Przy równych priorytetach zachowywana jest kolejność wstawiania (FIFO).\n\n    Złożoność operacji:\n        push          -- O(log n)\n        pop           -- O(log n)\n        peek          -- O(1)\n        decrease_key  -- O(log n)\n        len           -- O(1)\n    \"\"\"\n\n    def __init__(self) -> None:\n        \"\"\"Tworzy pustą kolejkę priorytetową.\"\"\"\n        # Kopiec: lista wpisów postaci [priorytet, licznik, element].\n        self._heap: List[List[Any]] = []\n        # Mapa: element -> indeks jego wpisu w liście kopca.\n        self._pos: Dict[Hashable, int] = {}\n        # Rosnący licznik służący do rozstrzygania remisów (stabilność FIFO).\n        self._counter: int = 0\n\n    # ------------------------------------------------------------------\n    # Interfejs publiczny\n    # ------------------------------------------------------------------\n\n    def __len__(self) -> int:\n        \"\"\"Zwraca liczbę elementów w kolejce.\"\"\"\n        return len(self._heap)\n\n    def __contains__(self, item: Hashable) -> bool:\n        \"\"\"Sprawdza, czy element znajduje się w kolejce.\"\"\"\n        return item in self._pos\n\n    def push(self, item: Hashable, priority: Any) -> None:\n        \"\"\"Dodaje element o zadanym priorytecie.\n\n        Args:\n            item: Element do wstawienia (haszowalny, unikalny w kolejce).\n            priority: Priorytet elementu (mniejsza wartość = wyższy priorytet).\n\n        Raises:\n            ValueError: Jeśli element już znajduje się w kolejce.\n        \"\"\"\n        if item in self._pos:\n            raise ValueError(f\"Element {item!r} już znajduje się w kolejce\")\n\n        # Dopisujemy wpis na koniec kopca i przesiewamy go w górę.\n        entry = [priority, self._counter, item]\n        self._counter += 1\n        self._heap.append(entry)\n        self._pos[item] = len(self._heap) - 1\n        self._sift_up(len(self._heap) - 1)\n\n    def pop(self) -> Tuple[Hashable, Any]:\n        \"\"\"Usuwa i zwraca element o najmniejszym priorytecie.\n\n        Returns:\n            Krotka ``(element, priorytet)``.\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"pop z pustej kolejki priorytetowej\")\n\n        last = len(self._heap) - 1\n        # Zamieniamy korzeń z ostatnim wpisem, aby usunąć go w czasie O(1).\n        self._swap(0, last)\n        priority, _, item = self._heap.pop()\n        del self._pos[item]\n\n        # Przywracamy własność kopca, przesiewając nowy korzeń w dół.\n        if self._heap:\n            self._sift_down(0)\n\n        return item, priority\n\n    def peek(self) -> Tuple[Hashable, Any]:\n        \"\"\"Zwraca (bez usuwania) element o najmniejszym priorytecie.\n\n        Returns:\n            Krotka ``(element, priorytet)``.\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"peek na pustej kolejce priorytetowej\")\n        priority, _, item = self._heap[0]\n        return item, priority\n\n    def decrease_key(self, item: Hashable, new_priority: Any) -> None:\n        \"\"\"Zmniejsza priorytet elementu znajdującego się w kolejce.\n\n        Args:\n            item: Element, którego priorytet ma zostać zmniejszony.\n            new_priority: Nowy priorytet (nie większy od obecnego).\n\n        Raises:\n            KeyError: Jeśli elementu nie ma w kolejce.\n            ValueError: Jeśli nowy priorytet jest większy od obecnego.\n        \"\"\"\n        if item not in self._pos:\n            raise KeyError(f\"Elementu {item!r} nie ma w kolejce\")\n\n        index = self._pos[item]\n        entry = self._heap[index]\n\n        if new_priority > entry[0]:\n            raise ValueError(\n                \"Nowy priorytet nie może być większy od obecnego\"\n            )\n\n        # Po zmniejszeniu klucza element może jedynie wędrować w górę kopca.\n        entry[0] = new_priority\n        self._sift_up(index)\n\n    # ------------------------------------------------------------------\n    # Metody pomocnicze\n    # ------------------------------------------------------------------\n\n    def _less(self, i: int, j: int) -> bool:\n        \"\"\"Sprawdza, czy wpis o indeksie ``i`` jest mniejszy od wpisu ``j``.\n\n        Porównywana jest para (priorytet, licznik), więc elementy nigdy nie są\n        porównywane bezpośrednio, a remisy rozstrzyga kolejność wstawiania.\n        \"\"\"\n        a, b = self._heap[i], self._heap[j]\n        return (a[0], a[1]) < (b[0], b[1])\n\n    def _swap(self, i: int, j: int) -> None:\n        \"\"\"Zamienia miejscami dwa wpisy i aktualizuje mapę pozycji.\"\"\"\n        heap = self._heap\n        heap[i], heap[j] = heap[j], heap[i]\n        self._pos[heap[i][2]] = i\n        self._pos[heap[j][2]] = j\n\n    def _sift_up(self, index: int) -> None:\n        \"\"\"Przesiewa wpis w górę, dopóki jest mniejszy od swojego rodzica.\"\"\"\n        while index > 0:\n            parent = (index - 1) // 2\n            if self._less(index, parent):\n                self._swap(index, parent)\n                index = parent\n            else:\n                break\n\n    def _sift_down(self, index: int) -> None:\n        \"\"\"Przesiewa wpis w dół, dopóki jest większy od któregoś z dzieci.\"\"\"\n        size = len(self._heap)\n        while True:\n            left = 2 * index + 1\n            right = left + 1\n            smallest = index\n\n            # Wybieramy najmniejszy spośród: bieżący węzeł, lewe i prawe dziecko.\n            if left < size and self._less(left, smallest):\n                smallest = left\n            if right < size and self._less(right, smallest):\n                smallest = right\n\n            if smallest == index:\n                break  # Własność kopca jest zachowana.\n\n            self._swap(index, smallest)\n            index = smallest\n```",
   "czas_s": 23.968,
   "usage": {
    "prompt_tokens": 92,
    "completion_tokens": 2806,
    "total_tokens": 2898,
    "cost": 0.112976,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.112976,
     "upstream_inference_prompt_cost": 0.000736,
     "upstream_inference_completions_cost": 0.11224
    },
    "completion_tokens_details": {
     "reasoning_tokens": 374,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.112976,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.112976,
   "ttft_s": 6.115,
   "ttft_dowolny_token_s": 4.113,
   "gen_s": 19.855,
   "tok_s": 141.3,
   "slowa": 573,
   "slowa_na_s_calkowity_czas": 23.9,
   "tokeny": {
    "wej": 92,
    "wyj": 2806,
    "rozumowanie": 374
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S3_tlumaczenie#1",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Przetłumacz na angielski, zachowując akapity:\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "The Brzeziny Górne Town Council adopted the \"Rower za rogiem\" (\"A Bike Around the Corner\") programme on Wednesday. From 1 March 2027, the town is to have 14 public bike stations, including four at secondary schools and two at the railway station. A total of 160 bikes will be available for hire, 40 of them with electric assistance. The first 20 minutes of each ride will be free, and each additional hour or part thereof is to cost 3 złoty; for an electric bike, 5 złoty.\n\nThe programme will cost 2.1 million złoty over three years. Half will be covered by a grant from the provincial fund, and the rest by the town budget. The operator will be a company selected in a tender that is to be announced by the end of December. Opposition councillors voted against, pointing out that the town still lacks dedicated cycle paths: there are currently 11 km of them, and according to the plan there are to be 26 km by 2028. The mayor responds that the public bikes and the new routes will be developed in parallel, and that the first 6 km of new paths will be opened before the bike hire scheme launches.\n\nAlso present at the session were residents of the Zatorze housing estate, which will not get a station. Officials promised that after the first season they would analyse the rental data and consider expanding the network. A survey conducted in the summer showed that 38 per cent of adult residents would like to use a public bike, most often on their way to work or to catch a train.",
   "czas_s": 5.951,
   "usage": {
    "prompt_tokens": 690,
    "completion_tokens": 464,
    "total_tokens": 1154,
    "cost": 0.02408,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.02408,
     "upstream_inference_prompt_cost": 0.00552,
     "upstream_inference_completions_cost": 0.01856
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.02408,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.02408,
   "ttft_s": 2.234,
   "ttft_dowolny_token_s": 2.234,
   "gen_s": 3.718,
   "tok_s": 124.8,
   "slowa": 271,
   "slowa_na_s_calkowity_czas": 45.5,
   "tokeny": {
    "wej": 690,
    "wyj": 464,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S1_esej#2",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Napisz po polsku tekst na około 500 słów o tym, jak działa kompostownik w ogrodzie. Bez wstępów i podsumowań typu „podsumowując”.",
   "odpowiedz": "Kompostownik to miejsce, w którym resztki roślinne i kuchenne zamieniają się w żyzną, ciemną ziemię dzięki pracy miliardów mikroorganizmów. Bakterie, grzyby i promieniowce rozkładają materię organiczną, a pomagają im w tym dżdżownice, stonogi, skoczogonki i inne drobne zwierzęta glebowe. Aby ten proces przebiegał sprawnie, organizmy potrzebują czterech rzeczy: pożywienia bogatego w węgiel, pożywienia bogatego w azot, wody i powietrza.\n\nMateriały wrzucane do kompostownika dzieli się zwyczajowo na „brązowe” i „zielone”. Brązowe, czyli suche liście, słoma, rozdrobnione gałązki, trociny czy tektura, dostarczają węgla, który jest dla mikrobów źródłem energii. Zielone, czyli skoszona trawa, obierki warzyw i owoców, fusy z kawy czy świeże chwasty, zawierają dużo azotu potrzebnego do budowy białek i namnażania się bakterii. Najlepiej układać je warstwami lub mieszać, zachowując mniej więcej dwie części brązowych na jedną część zielonych. Nadmiar zielonych sprawia, że pryzma gnije i wydziela zapach amoniaku, a nadmiar brązowych bardzo spowalnia rozkład.\n\nW dobrze ułożonej pryzmie proces przebiega etapami. Najpierw bakterie mezofilne rzucają się na łatwo dostępne cukry i białka, a ich intensywna przemiana materii wytwarza ciepło. W ciągu kilku dni temperatura wewnątrz może wzrosnąć do 50–65 stopni Celsjusza. Rozpoczyna się wtedy faza termofilna, w której ciepłolubne bakterie rozkładają tłuszcze, celulozę i inne trudniejsze związki. Wysoka temperatura niszczy przy okazji większość nasion chwastów i zarazków chorób roślin. Gdy łatwo przyswajalny pokarm się wyczerpuje, pryzma stopniowo stygnie. Do głosu dochodzą grzyby i promieniowce, które radzą sobie z ligniną zawartą w zdrewniałych częściach roślin, a do środka wkraczają dżdżownice kompostowe. Przepuszczają one materię przez przewód pokarmowy, mieszają ją i wzbogacają o cenne związki. Ostatnia faza to dojrzewanie, podczas którego powstaje stabilna próchnica.\n\nOgromne znaczenie ma wilgotność. Materiał powinien przypominać w dotyku wyciśniętą gąbkę: być wilgotny, ale nie ociekać wodą. Zbyt sucha pryzma zamiera, ponieważ mikroorganizmy żyją w cienkiej warstewce wody otaczającej cząstki materii. Zbyt mokra traci dostęp do tlenu, a wtedy zaczynają dominować bakterie beztlenowe, wytwarzające cuchnące gazy. Dlatego w czasie suszy kompost warto podlać, a podczas długotrwałych deszczów przykryć.\n\nRównie ważne jest napowietrzanie. Kompostownik powinien mieć szczeliny w ściankach, a na dnie dobrze ułożyć warstwę grubszych gałęzi, która działa jak drenaż i kanał wentylacyjny. Przerzucanie pryzmy widłami co kilka tygodni dostarcza tlenu do środka, przenosi zewnętrzne, słabiej rozłożone warstwy do gorącego wnętrza i wyraźnie przyspiesza cały proces. Kompostownik stawia się bezpośrednio na ziemi, w półcieniu, aby organizmy glebowe miały do niego swobodny dostęp, a słońce nie wysuszało zawartości.\n\nNie wszystko nadaje się do kompostowania. Mięso, kości, nabiał i tłuste resztki przyciągają gryzonie i gniją. Należy też unikać roślin porażonych chorobami, chwastów z dojrzałymi nasionami, odchodów psów i kotów oraz drewna malowanego lub impregnowanego. Rozdrabnianie materiału przed wrzuceniem zwiększa powierzchnię dostępną dla mikroorganizmów, więc pocięte gałązki i liście rozkładają się wielokrotnie szybciej niż całe.\n\nW zależności od pielęgnacji i pory roku dojrzały kompost powstaje po kilku miesiącach lub nawet po dwóch latach. Gotowy jest ciemnobrązowy, sypki, pachnie ściółką leśną i nie da się w nim rozpoznać pierwotnych składników. Rozłożony na grządkach poprawia strukturę gleby, zwiększa jej zdolność zatrzymywania wody i powoli uwalnia azot, fosfor oraz potas, którymi rośliny żywią się przez cały sezon.",
   "czas_s": 19.523,
   "usage": {
    "prompt_tokens": 70,
    "completion_tokens": 1791,
    "total_tokens": 1861,
    "cost": 0.0722,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.0722,
     "upstream_inference_prompt_cost": 0.00056,
     "upstream_inference_completions_cost": 0.07164
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.0722,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.0722,
   "ttft_s": 2.189,
   "ttft_dowolny_token_s": 2.189,
   "gen_s": 17.333,
   "tok_s": 103.3,
   "slowa": 504,
   "slowa_na_s_calkowity_czas": 25.8,
   "tokeny": {
    "wej": 70,
    "wyj": 1791,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S2_kod#2",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Napisz w Pythonie kompletną implementację kolejki priorytetowej opartej na kopcu binarnym (klasa z metodami push, pop, peek, __len__, decrease_key) z docstringami i komentarzami. Tylko kod.",
   "odpowiedz": "```python\n\"\"\"Kolejka priorytetowa oparta na kopcu binarnym (min-heap).\n\nElement o najmniejszej wartości priorytetu jest zwracany jako pierwszy.\nImplementacja utrzymuje dodatkowy słownik pozycji, dzięki czemu operacja\ndecrease_key działa w czasie O(log n).\n\"\"\"\n\nfrom typing import Any, Dict, Hashable, List, Tuple\n\n\nclass PriorityQueue:\n    \"\"\"Kolejka priorytetowa (min-heap) z obsługą operacji decrease_key.\n\n    Elementy muszą być haszowalne i unikalne w obrębie kolejki.\n    Priorytety muszą być wzajemnie porównywalne (np. liczby).\n\n    Złożoność operacji:\n        push          -- O(log n)\n        pop           -- O(log n)\n        peek          -- O(1)\n        decrease_key  -- O(log n)\n        __len__       -- O(1)\n        __contains__  -- O(1)\n\n    Przykład:\n        >>> pq = PriorityQueue()\n        >>> pq.push(\"a\", 5)\n        >>> pq.push(\"b\", 2)\n        >>> pq.push(\"c\", 8)\n        >>> pq.decrease_key(\"c\", 1)\n        >>> pq.pop()\n        ('c', 1)\n        >>> pq.peek()\n        ('b', 2)\n        >>> len(pq)\n        2\n    \"\"\"\n\n    def __init__(self) -> None:\n        \"\"\"Tworzy pustą kolejkę priorytetową.\"\"\"\n        # Kopiec przechowywany w liście jako pary [priorytet, element].\n        # Dla węzła o indeksie i:\n        #   rodzic      -> (i - 1) // 2\n        #   lewe dziecko  -> 2 * i + 1\n        #   prawe dziecko -> 2 * i + 2\n        self._heap: List[List[Any]] = []\n        # Słownik: element -> jego aktualny indeks w liście kopca.\n        self._position: Dict[Hashable, int] = {}\n\n    # ------------------------------------------------------------------\n    # Interfejs publiczny\n    # ------------------------------------------------------------------\n\n    def __len__(self) -> int:\n        \"\"\"Zwraca liczbę elementów w kolejce.\"\"\"\n        return len(self._heap)\n\n    def __contains__(self, item: Hashable) -> bool:\n        \"\"\"Sprawdza, czy element znajduje się w kolejce.\"\"\"\n        return item in self._position\n\n    def push(self, item: Hashable, priority: Any) -> None:\n        \"\"\"Dodaje element do kolejki z podanym priorytetem.\n\n        Args:\n            item: Element do dodania (haszowalny, unikalny w kolejce).\n            priority: Priorytet elementu (mniejsza wartość = wyższy priorytet).\n\n        Raises:\n            ValueError: Jeśli element już znajduje się w kolejce.\n        \"\"\"\n        if item in self._position:\n            raise ValueError(f\"Element {item!r} już znajduje się w kolejce\")\n\n        # Nowy element trafia na koniec kopca, a następnie jest\n        # przesiewany w górę, aż własność kopca zostanie przywrócona.\n        index = len(self._heap)\n        self._heap.append([priority, item])\n        self._position[item] = index\n        self._sift_up(index)\n\n    def pop(self) -> Tuple[Hashable, Any]:\n        \"\"\"Usuwa i zwraca element o najmniejszym priorytecie.\n\n        Returns:\n            Krotka (element, priorytet).\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"pop z pustej kolejki priorytetowej\")\n\n        # Zamieniamy korzeń z ostatnim elementem, usuwamy ostatni\n        # (czyli dawny korzeń) i przesiewamy nowy korzeń w dół.\n        last = len(self._heap) - 1\n        self._swap(0, last)\n        priority, item = self._heap.pop()\n        del self._position[item]\n\n        if self._heap:\n            self._sift_down(0)\n\n        return item, priority\n\n    def peek(self) -> Tuple[Hashable, Any]:\n        \"\"\"Zwraca (bez usuwania) element o najmniejszym priorytecie.\n\n        Returns:\n            Krotka (element, priorytet).\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"peek z pustej kolejki priorytetowej\")\n\n        priority, item = self._heap[0]\n        return item, priority\n\n    def decrease_key(self, item: Hashable, new_priority: Any) -> None:\n        \"\"\"Zmniejsza priorytet elementu znajdującego się w kolejce.\n\n        Args:\n            item: Element, którego priorytet ma zostać zmniejszony.\n            new_priority: Nowy priorytet (nie większy od obecnego).\n\n        Raises:\n            KeyError: Jeśli elementu nie ma w kolejce.\n            ValueError: Jeśli nowy priorytet jest większy od obecnego.\n        \"\"\"\n        if item not in self._position:\n            raise KeyError(f\"Elementu {item!r} nie ma w kolejce\")\n\n        index = self._position[item]\n        if new_priority > self._heap[index][0]:\n            raise ValueError(\n                \"Nowy priorytet nie może być większy od obecnego \"\n                f\"({new_priority!r} > {self._heap[index][0]!r})\"\n            )\n\n        # Po zmniejszeniu klucza element może naruszać własność kopca\n        # jedynie względem swoich przodków, więc wystarczy przesiać w górę.\n        self._heap[index][0] = new_priority\n        self._sift_up(index)\n\n    # ------------------------------------------------------------------\n    # Metody pomocnicze\n    # ------------------------------------------------------------------\n\n    def _swap(self, i: int, j: int) -> None:\n        \"\"\"Zamienia miejscami dwa węzły kopca i aktualizuje słownik pozycji.\"\"\"\n        if i == j:\n            return\n        heap = self._heap\n        heap[i], heap[j] = heap[j], heap[i]\n        self._position[heap[i][1]] = i\n        self._position[heap[j][1]] = j\n\n    def _sift_up(self, index: int) -> None:\n        \"\"\"Przesiewa węzeł w górę, dopóki jest mniejszy od swojego rodzica.\"\"\"\n        heap = self._heap\n        while index > 0:\n            parent = (index - 1) // 2\n            if heap[index][0] < heap[parent][0]:\n                self._swap(index, parent)\n                index = parent\n            else:\n                break  # własność kopca zachowana\n\n    def _sift_down(self, index: int) -> None:\n        \"\"\"Przesiewa węzeł w dół, dopóki jest większy od któregoś z dzieci.\"\"\"\n        heap = self._heap\n        size = len(heap)\n        while True:\n            left = 2 * index + 1\n            right = left + 1\n            smallest = index\n\n            # Wybieramy najmniejszy spośród: węzła, lewego i prawego dziecka.\n            if left < size and heap[left][0] < heap[smallest][0]:\n                smallest = left\n            if right < size and heap[right][0] < heap[smallest][0]:\n                smallest = right\n\n            if smallest == index:\n                break  # własność kopca zachowana\n\n            self._swap(index, smallest)\n            index = smallest\n```",
   "czas_s": 21.019,
   "usage": {
    "prompt_tokens": 92,
    "completion_tokens": 2512,
    "total_tokens": 2604,
    "cost": 0.101216,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.101216,
     "upstream_inference_prompt_cost": 0.000736,
     "upstream_inference_completions_cost": 0.10048
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.101216,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.101216,
   "ttft_s": 2.41,
   "ttft_dowolny_token_s": 2.41,
   "gen_s": 18.609,
   "tok_s": 135.0,
   "slowa": 576,
   "slowa_na_s_calkowity_czas": 27.4,
   "tokeny": {
    "wej": 92,
    "wyj": 2512,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S3_tlumaczenie#2",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Przetłumacz na angielski, zachowując akapity:\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "The Brzeziny Górne Town Council adopted the \"Rower za rogiem\" (\"A Bike Around the Corner\") programme on Wednesday. From 1 March 2027, the town is to have 14 public bike stations, including four at secondary schools and two at the railway station. A total of 160 bikes will be available for hire, 40 of them electrically assisted. The first 20 minutes of each ride will be free, and each subsequent hour or part thereof is to cost 3 złoty; for an electric bike, 5 złoty.\n\nThe programme will cost 2.1 million złoty over three years. Half will be covered by a grant from the provincial fund, the rest by the town budget. The operator will be a company selected through a tender, which is to be announced by the end of December. Opposition councillors voted against, pointing out that the town still lacks dedicated cycle paths: there are currently 11 km of them, and according to the plan there are to be 26 km by 2028. The mayor responds that the bike-share scheme and the new routes will be developed in parallel, and that the first 6 km of new paths will be opened before the hire scheme launches.\n\nAlso present at the session were residents of the Zatorze housing estate, which will not be getting any station. Officials promised that after the first season they would analyse the rental data and consider expanding the network. A survey conducted over the summer showed that 38 per cent of adult residents would like to use the public bikes, most often on their way to work or to catch a train.",
   "czas_s": 6.284,
   "usage": {
    "prompt_tokens": 690,
    "completion_tokens": 470,
    "total_tokens": 1160,
    "cost": 0.02432,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.02432,
     "upstream_inference_prompt_cost": 0.00552,
     "upstream_inference_completions_cost": 0.0188
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.02432,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.02432,
   "ttft_s": 2.243,
   "ttft_dowolny_token_s": 2.243,
   "gen_s": 4.04,
   "tok_s": 116.3,
   "slowa": 269,
   "slowa_na_s_calkowity_czas": 42.8,
   "tokeny": {
    "wej": 690,
    "wyj": 470,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S1_esej#1",
   "model": "anthropic/claude-fable-5.1",
   "provider": "Anthropic",
   "prompt": "Napisz po polsku tekst na około 500 słów o tym, jak działa kompostownik w ogrodzie. Bez wstępów i podsumowań typu „podsumowując”.",
   "odpowiedz": "Kompostownik to miejsce, w którym odpady organiczne z ogrodu i kuchni zamieniają się w próchnicę – ciemną, kruchą masę o zapachu leśnej ściółki. Proces ten napędzają mikroorganizmy: bakterie, grzyby i promieniowce, a wspomagają je dżdżownice, roztocza, skoczogonki oraz larwy owadów. Wszystkie te organizmy rozkładają materię roślinną na prostsze związki, które później stają się pokarmem dla roślin uprawnych.\n\nPodstawą prawidłowego kompostowania jest równowaga między dwoma rodzajami materiału. Pierwszy to tak zwane składniki „zielone”, bogate w azot: skoszona trawa, resztki warzyw i owoców, fusy z kawy, chwasty bez nasion, obierki, zwiędłe kwiaty. Drugi to składniki „brązowe”, zasobne w węgiel: suche liście, rozdrobnione gałęzie, słoma, trociny z nieimpregnowanego drewna, kartonowe opakowania po jajkach, papier bez kolorowych nadruków. Mikroorganizmy potrzebują obu grup – azot pozwala im się namnażać, węgiel dostarcza energii. Dobrze działający kompost zawiera mniej więcej jedną część materiału zielonego na dwie lub trzy części brązowego. Zbyt dużo zieleni powoduje gnicie i nieprzyjemny zapach amoniaku, nadmiar brązu sprawia, że rozkład niemal zamiera.\n\nDrugim warunkiem jest dostęp powietrza. Bakterie tlenowe pracują szybko i nie wytwarzają smrodu, dlatego pryzma nie powinna być zbita. Pomaga układanie warstwami, mieszanie grubszych fragmentów z drobnymi oraz przerzucanie całości widłami co kilka tygodni. Kompostowniki drewniane buduje się ze szczelinami między deskami, a plastikowe mają otwory wentylacyjne właśnie po to, by tlen docierał do wnętrza. Dno kompostownika powinno stykać się z glebą – stamtąd przychodzą dżdżownice i pożyteczne mikroorganizmy, a nadmiar wody może swobodnie odpłynąć.\n\nTrzecim czynnikiem jest wilgotność. Zawartość powinna przypominać wyciśniętą gąbkę: wilgotną, ale nie ociekającą wodą. W suche lato pryzmę warto podlać, a w deszczowe miesiące przykryć matą, agrowłókniną lub kawałkiem tektury. Przesuszony kompost przestaje pracować, przemoczony zaczyna beztlenowo fermentować.\n\nGdy warunki są odpowiednie, wewnątrz pryzmy temperatura rośnie. W pierwszych dniach może przekroczyć 60 stopni Celsjusza – to efekt intensywnej pracy bakterii termofilnych. Taki żar niszczy nasiona chwastów i część patogenów. Po kilku tygodniach masa stygnie, a do akcji wkraczają grzyby i większe organizmy glebowe, które dokańczają rozkład twardszych tkanek, takich jak łodygi czy drobne gałązki. Materiał kurczy się przy tym wyraźnie, nawet o połowę objętości.\n\nNie wszystko nadaje się do kompostownika. Mięso, ryby, nabiał i tłuste resztki przyciągają gryzonie i wydzielają odór. Chore liście, na przykład porażone parchem czy mączniakiem, mogą przenieść patogeny na kolejne sezony. Odchody psów i kotów zawierają groźne pasożyty. Skórki cytrusów rozkładają się bardzo wolno, a popiół z węgla zawiera szkodliwe związki. Z kolei popiół drzewny w niewielkiej ilości wzbogaca kompost w potas i wapń.\n\nDojrzały kompost powstaje zwykle po sześciu do dwunastu miesiącach, w zależności od pory roku, rozdrobnienia materiału i częstotliwości przerzucania. Poznaje się go po jednolitej, ciemnobrązowej barwie, sypkiej strukturze i braku rozpoznawalnych resztek poza drobnymi kawałkami drewna. Przesiewa się go przez siatkę o oczkach około centymetra, a to, co zostanie na sicie, wraca do kompostownika jako zaczyn dla kolejnej partii.\n\nGotowy kompost rozsypuje się warstwą jednego lub dwóch centymetrów wokół roślin, miesza z ziemią w grządkach lub dodaje do podłoża w doniczkach. Poprawia strukturę gleby, zwiększa jej zdolność zatrzymywania wody, dostarcza składników pokarmowych uwalnianych powoli i równomiernie, a także zasiedla ziemię pożytecznymi mikroorganizmami, które chronią korzenie przed chorobami.",
   "czas_s": 27.964,
   "usage": {
    "prompt_tokens": 70,
    "completion_tokens": 1788,
    "total_tokens": 1858,
    "cost": 0.0901,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.0901,
     "upstream_inference_prompt_cost": 0.0007,
     "upstream_inference_completions_cost": 0.0894
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.0901,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.0901,
   "ttft_s": 2.009,
   "ttft_dowolny_token_s": 2.009,
   "gen_s": 25.955,
   "tok_s": 68.9,
   "slowa": 505,
   "slowa_na_s_calkowity_czas": 18.1,
   "tokeny": {
    "wej": 70,
    "wyj": 1788,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S2_kod#1",
   "model": "anthropic/claude-fable-5.1",
   "provider": "Anthropic",
   "prompt": "Napisz w Pythonie kompletną implementację kolejki priorytetowej opartej na kopcu binarnym (klasa z metodami push, pop, peek, __len__, decrease_key) z docstringami i komentarzami. Tylko kod.",
   "odpowiedz": "```python\nclass PriorityQueue:\n    \"\"\"\n    Kolejka priorytetowa oparta na kopcu binarnym typu min-heap.\n\n    Element o najmniejszym priorytecie znajduje się zawsze na szczycie kopca\n    (indeks 0). Każdy element przechowywany jest jako para (priorytet, wartość).\n    Dzięki słownikowi `_index` możliwe jest szybkie odnalezienie pozycji\n    elementu w kopcu, co umożliwia operację decrease_key w czasie O(log n).\n\n    Założenie: wartości (klucze) elementów są unikalne i hashowalne.\n\n    Złożoności:\n        push          - O(log n)\n        pop           - O(log n)\n        peek          - O(1)\n        __len__       - O(1)\n        decrease_key  - O(log n)\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"Inicjalizuje pustą kolejkę priorytetową.\"\"\"\n        # Lista reprezentująca kopiec: każdy element to [priorytet, wartość].\n        # Używamy listy (a nie krotki), aby móc zmieniać priorytet w miejscu.\n        self._heap = []\n        # Mapa: wartość -> indeks w tablicy _heap (do szybkiego decrease_key).\n        self._index = {}\n\n    # ------------------------------------------------------------------ #\n    # Metody pomocnicze\n    # ------------------------------------------------------------------ #\n\n    @staticmethod\n    def _parent(i):\n        \"\"\"Zwraca indeks rodzica węzła o indeksie i.\"\"\"\n        return (i - 1) // 2\n\n    @staticmethod\n    def _left(i):\n        \"\"\"Zwraca indeks lewego dziecka węzła o indeksie i.\"\"\"\n        return 2 * i + 1\n\n    @staticmethod\n    def _right(i):\n        \"\"\"Zwraca indeks prawego dziecka węzła o indeksie i.\"\"\"\n        return 2 * i + 2\n\n    def _swap(self, i, j):\n        \"\"\"Zamienia miejscami elementy o indeksach i oraz j i aktualizuje mapę indeksów.\"\"\"\n        self._heap[i], self._heap[j] = self._heap[j], self._heap[i]\n        self._index[self._heap[i][1]] = i\n        self._index[self._heap[j][1]] = j\n\n    def _sift_up(self, i):\n        \"\"\"\n        Przesuwa element o indeksie i w górę kopca, dopóki jego priorytet\n        jest mniejszy od priorytetu rodzica. Przywraca własność kopca po\n        wstawieniu lub zmniejszeniu klucza.\n        \"\"\"\n        while i > 0:\n            p = self._parent(i)\n            if self._heap[i][0] < self._heap[p][0]:\n                self._swap(i, p)\n                i = p\n            else:\n                break\n\n    def _sift_down(self, i):\n        \"\"\"\n        Przesuwa element o indeksie i w dół kopca, zamieniając go z mniejszym\n        z dzieci, dopóki własność kopca nie zostanie przywrócona.\n        \"\"\"\n        n = len(self._heap)\n        while True:\n            left = self._left(i)\n            right = self._right(i)\n            smallest = i\n\n            # Wybieramy najmniejszy spośród węzła i jego dzieci.\n            if left < n and self._heap[left][0] < self._heap[smallest][0]:\n                smallest = left\n            if right < n and self._heap[right][0] < self._heap[smallest][0]:\n                smallest = right\n\n            if smallest == i:\n                break  # Własność kopca zachowana.\n            self._swap(i, smallest)\n            i = smallest\n\n    # ------------------------------------------------------------------ #\n    # Interfejs publiczny\n    # ------------------------------------------------------------------ #\n\n    def push(self, priority, value):\n        \"\"\"\n        Wstawia element do kolejki.\n\n        Args:\n            priority: priorytet elementu (im mniejszy, tym wcześniej zostanie zdjęty).\n            value: unikalna, hashowalna wartość identyfikująca element.\n\n        Raises:\n            ValueError: jeśli wartość już znajduje się w kolejce.\n        \"\"\"\n        if value in self._index:\n            raise ValueError(f\"Element {value!r} już znajduje się w kolejce.\")\n        # Dodajemy element na koniec kopca i przesuwamy go w górę.\n        self._heap.append([priority, value])\n        idx = len(self._heap) - 1\n        self._index[value] = idx\n        self._sift_up(idx)\n\n    def pop(self):\n        \"\"\"\n        Usuwa i zwraca element o najmniejszym priorytecie.\n\n        Returns:\n            Krotka (priorytet, wartość).\n\n        Raises:\n            IndexError: jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"pop z pustej kolejki priorytetowej\")\n\n        top = self._heap[0]\n        last = self._heap.pop()  # Zdejmujemy ostatni element.\n        del self._index[top[1]]\n\n        if self._heap:\n            # Ostatni element trafia na szczyt, a następnie jest przesuwany w dół.\n            self._heap[0] = last\n            self._index[last[1]] = 0\n            self._sift_down(0)\n\n        return top[0], top[1]\n\n    def peek(self):\n        \"\"\"\n        Zwraca element o najmniejszym priorytecie bez usuwania go.\n\n        Returns:\n            Krotka (priorytet, wartość).\n\n        Raises:\n            IndexError: jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"peek z pustej kolejki priorytetowej\")\n        priority, value = self._heap[0]\n        return priority, value\n\n    def decrease_key(self, value, new_priority):\n        \"\"\"\n        Zmniejsza priorytet istniejącego elementu.\n\n        Args:\n            value: wartość identyfikująca element w kolejce.\n            new_priority: nowy priorytet; musi być mniejszy lub równy obecnemu.\n\n        Raises:\n            KeyError: jeśli element nie istnieje w kolejce.\n            ValueError: jeśli nowy priorytet jest większy od obecnego.\n        \"\"\"\n        if value not in self._index:\n            raise KeyError(f\"Element {value!r} nie znajduje się w kolejce.\")\n        idx = self._index[value]\n        if new_priority > self._heap[idx][0]:\n            raise ValueError(\"Nowy priorytet musi być mniejszy lub równy obecnemu.\")\n        # Aktualizujemy priorytet i przywracamy własność kopca przesuwając w górę.\n        self._heap[idx][0] = new_priority\n        self._sift_up(idx)\n\n    def __len__(self):\n        \"\"\"Zwraca liczbę elementów w kolejce.\"\"\"\n        return len(self._heap)\n\n    def __bool__(self):\n        \"\"\"Zwraca True, jeśli kolejka nie jest pusta.\"\"\"\n        return bool(self._heap)\n\n    def __contains__(self, value):\n        \"\"\"Sprawdza, czy element o danej wartości znajduje się w kolejce.\"\"\"\n        return value in self._index\n\n    def __repr__(self):\n        \"\"\"Zwraca tekstową reprezentację kolejki (zawartość kopca).\"\"\"\n        return f\"PriorityQueue({[(p, v) for p, v in self._heap]})\"\n\n\nif __name__ == \"__main__\":\n    # Przykładowe użycie\n    pq = PriorityQueue()\n    pq.push(5, \"e\")\n    pq.push(3, \"c\")\n    pq.push(8, \"h\")\n    pq.push(1, \"a\")\n\n    print(len(pq))          # 4\n    print(pq.peek())        # (1, 'a')\n\n    pq.decrease_key(\"h\", 0) # 'h' staje się najważniejszy\n    print(pq.peek())        # (0, 'h')\n\n    while pq:\n        print(pq.pop())     # (0,'h'), (1,'a'), (3,'c'), (5,'e')\n```",
   "czas_s": 27.68,
   "usage": {
    "prompt_tokens": 92,
    "completion_tokens": 2680,
    "total_tokens": 2772,
    "cost": 0.13492,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.13492,
     "upstream_inference_prompt_cost": 0.00092,
     "upstream_inference_completions_cost": 0.134
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.13492,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.13492,
   "ttft_s": 1.857,
   "ttft_dowolny_token_s": 1.857,
   "gen_s": 25.823,
   "tok_s": 103.8,
   "slowa": 612,
   "slowa_na_s_calkowity_czas": 22.1,
   "tokeny": {
    "wej": 92,
    "wyj": 2680,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S3_tlumaczenie#1",
   "model": "anthropic/claude-fable-5.1",
   "provider": "Azure",
   "prompt": "Przetłumacz na angielski, zachowując akapity:\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "On Wednesday, the Brzeziny Górne City Council adopted the \"Bike Around the Corner\" programme. From 1 March 2027, the city is to have 14 city bike stations, including four at secondary schools and two at the railway station. A total of 160 bikes will be available for hire, 40 of them with electric assistance. The first 20 minutes of each ride will be free, and each subsequent hour or part thereof is to cost 3 złoty; for an electric bike, 5 złoty.\n\nThe programme costs PLN 2.1 million over three years. Half will be covered by a grant from the provincial fund, the rest by the city budget. The operator will be a company selected in a tender that is to be announced by the end of December. Opposition councillors voted against, pointing out that the city still lacks segregated cycle paths: there are currently 11 km of them, and according to the plan there are to be 26 km by 2028. The mayor replies that the city bikes and the new routes will be developed in parallel, and that the first 6 km of new paths will be opened before the hire scheme even launches.\n\nResidents of the Zatorze estate, which will not get any station, also appeared at the session. Officials promised that after the first season they would analyse the hire data and consider expanding the network. A survey carried out in the summer showed that 38 percent of adult residents would like to use the city bikes, most often on their way to work or to catch a train.",
   "czas_s": 8.574,
   "usage": {
    "prompt_tokens": 690,
    "completion_tokens": 450,
    "total_tokens": 1140,
    "cost": 0.0294,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.0294,
     "upstream_inference_prompt_cost": 0.0069,
     "upstream_inference_completions_cost": 0.0225
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.0294,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.0294,
   "ttft_s": 4.21,
   "ttft_dowolny_token_s": 4.21,
   "gen_s": 4.363,
   "tok_s": 103.1,
   "slowa": 263,
   "slowa_na_s_calkowity_czas": 30.7,
   "tokeny": {
    "wej": 690,
    "wyj": 450,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S1_esej#2",
   "model": "anthropic/claude-fable-5.1",
   "provider": "Google",
   "prompt": "Napisz po polsku tekst na około 500 słów o tym, jak działa kompostownik w ogrodzie. Bez wstępów i podsumowań typu „podsumowując”.",
   "odpowiedz": "Kompostownik to w gruncie rzeczy kontrolowany fragment naturalnego obiegu materii. Wszystko, co w nim zachodzi, dzieje się także na leśnym runie, tyle że w ogrodzie proces jest skupiony w jednym miejscu i przyspieszony przez odpowiednie warunki.\n\nGłównymi wykonawcami pracy są mikroorganizmy: bakterie, promieniowce i grzyby. To one rozkładają celulozę, białka, cukry i tłuszcze zawarte w resztkach roślinnych na prostsze związki. Do życia potrzebują czterech rzeczy: węgla, azotu, wody i tlenu. Zadaniem ogrodnika jest dostarczyć je w rozsądnych proporcjach, a resztę załatwi biologia.\n\nWęgiel pochodzi z materiałów suchych i brązowych – liści, słomy, rozdrobnionych gałęzi, tektury, trocin. Azot kryje się w materiałach zielonych i wilgotnych – skoszonej trawie, obierkach warzyw, fusach z kawy, świeżych chwastach. Przyjmuje się, że dobry kompost powstaje przy stosunku węgla do azotu około 25–30 do 1, co w praktyce oznacza mniej więcej dwie–trzy części brązowych na jedną część zielonych. Zbyt dużo azotu daje śmierdzącą, mazistą masę i ulatnianie się amoniaku. Zbyt dużo węgla sprawia, że pryzma leży latami niemal nietknięta.\n\nGdy proporcje są właściwe, a materiał umiarkowanie wilgotny – jak wyciśnięta gąbka – rozpoczyna się pierwsza faza. Bakterie mezofilne mnożą się gwałtownie, zjadając łatwo dostępne cukry i białka. Ich metabolizm wydziela ciepło, temperatura wewnątrz pryzmy rośnie, a po kilku dniach pałeczkę przejmują bakterie termofilne, działające w zakresie 45–70°C. W dużej, dobrze zbudowanej pryzmie można włożyć rękę i poczuć wyraźne gorąco, a zimą zobaczyć parę unoszącą się nad kompostem. Ta faza jest cenna, bo wysoka temperatura zabija nasiona chwastów, larwy szkodników i część patogenów roślinnych.\n\nTlen zużywa się szybko, zwłaszcza w środku. Dlatego kompost warto co kilka tygodni przerzucić widłami – napowietrzenie ożywia mikroorganizmy i wyrównuje rozkład, przenosząc zewnętrzne, chłodniejsze warstwy do gorącego wnętrza. Bez tlenu proces przechodzi w fermentację beztlenową: pojawiają się metan, siarkowodór i charakterystyczny zapach zgnilizny. Przerzucenie i dodanie suchego materiału zwykle naprawia sytuację.\n\nPo kilku tygodniach łatwo rozkładalne składniki się kończą, temperatura spada i zaczyna się faza dojrzewania. Do akcji wkraczają grzyby, rozkładające ligninę z drewna i łodyg, oraz cała fauna glebowa: dżdżownice, wije, skoczogonki, roztocza, larwy chrząszczy. Przepuszczają materiał przez swoje jelita, rozdrabniają go mechanicznie i mieszają z wydzielinami bogatymi w mikroorganizmy. Z chaosu resztek powstaje ciemna, grudkowata, pachnąca lasem próchnica.\n\nW tym czasie zachodzi też humifikacja – tworzenie się stabilnych związków próchnicznych, które w glebie przetrwają lata. To one poprawiają strukturę ziemi, zatrzymują wodę i składniki pokarmowe, a jednocześnie powoli uwalniają azot, fosfor, potas i mikroelementy.\n\nCały cykl w domowym kompostowniku trwa od kilku miesięcy do roku, zależnie od rozdrobnienia materiału, pory roku, wilgotności i tego, jak często pryzma jest mieszana. Gotowy kompost poznaje się po tym, że nie da się już rozpoznać, z czego powstał, ma jednolitą ciemną barwę i nie nagrzewa się po przerzuceniu. Objętość wsadu zmniejsza się przy tym mniej więcej o dwie trzecie – reszta uleciała jako dwutlenek węgla i para wodna, czyli dokładnie tak, jak w naturalnym obiegu pierwiastków.",
   "czas_s": 24.386,
   "usage": {
    "prompt_tokens": 70,
    "completion_tokens": 1645,
    "total_tokens": 1715,
    "cost": 0.08295,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.08295,
     "upstream_inference_prompt_cost": 0.0007,
     "upstream_inference_completions_cost": 0.08225
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.08295,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.08295,
   "ttft_s": 0.959,
   "ttft_dowolny_token_s": 0.959,
   "gen_s": 23.427,
   "tok_s": 70.2,
   "slowa": 465,
   "slowa_na_s_calkowity_czas": 19.1,
   "tokeny": {
    "wej": 70,
    "wyj": 1645,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S2_kod#2",
   "model": "anthropic/claude-fable-5.1",
   "provider": "Azure",
   "prompt": "Napisz w Pythonie kompletną implementację kolejki priorytetowej opartej na kopcu binarnym (klasa z metodami push, pop, peek, __len__, decrease_key) z docstringami i komentarzami. Tylko kod.",
   "odpowiedz": "```python\n\"\"\"\nKolejka priorytetowa oparta na kopcu binarnym (min-heap).\n\nElementy są przechowywane jako pary (priorytet, klucz). Dodatkowy słownik\n`_pos` mapuje klucz -> indeks w tablicy kopca, co umożliwia operację\ndecrease_key w czasie O(log n).\n\"\"\"\n\nfrom typing import Any, Hashable, List, Optional, Tuple\n\n\nclass PriorityQueue:\n    \"\"\"\n    Kolejka priorytetowa typu min-heap.\n\n    Element o najmniejszym priorytecie znajduje się zawsze na szczycie kopca.\n    Klucze muszą być hashowalne i unikalne w obrębie kolejki.\n\n    Złożoności:\n        push          - O(log n)\n        pop           - O(log n)\n        peek          - O(1)\n        decrease_key  - O(log n)\n        __len__       - O(1)\n        __contains__  - O(1)\n    \"\"\"\n\n    def __init__(self) -> None:\n        # Tablica kopca: lista par [priorytet, klucz].\n        # Używamy list zamiast krotek, aby móc modyfikować priorytet w miejscu.\n        self._heap: List[List[Any]] = []\n        # Mapa klucz -> indeks w tablicy kopca (potrzebna do decrease_key).\n        self._pos: dict = {}\n\n    # ------------------------------------------------------------------\n    # Pomocnicze operacje indeksowe\n    # ------------------------------------------------------------------\n\n    @staticmethod\n    def _parent(i: int) -> int:\n        \"\"\"Zwraca indeks rodzica węzła o indeksie i.\"\"\"\n        return (i - 1) // 2\n\n    @staticmethod\n    def _left(i: int) -> int:\n        \"\"\"Zwraca indeks lewego dziecka węzła o indeksie i.\"\"\"\n        return 2 * i + 1\n\n    @staticmethod\n    def _right(i: int) -> int:\n        \"\"\"Zwraca indeks prawego dziecka węzła o indeksie i.\"\"\"\n        return 2 * i + 2\n\n    def _swap(self, i: int, j: int) -> None:\n        \"\"\"Zamienia miejscami elementy o indeksach i oraz j i aktualizuje mapę pozycji.\"\"\"\n        self._heap[i], self._heap[j] = self._heap[j], self._heap[i]\n        self._pos[self._heap[i][1]] = i\n        self._pos[self._heap[j][1]] = j\n\n    # ------------------------------------------------------------------\n    # Przywracanie własności kopca\n    # ------------------------------------------------------------------\n\n    def _sift_up(self, i: int) -> None:\n        \"\"\"\n        Przesuwa element o indeksie i w górę kopca, dopóki jego priorytet\n        jest mniejszy niż priorytet rodzica.\n        \"\"\"\n        while i > 0:\n            p = self._parent(i)\n            if self._heap[i][0] < self._heap[p][0]:\n                self._swap(i, p)\n                i = p\n            else:\n                break\n\n    def _sift_down(self, i: int) -> None:\n        \"\"\"\n        Przesuwa element o indeksie i w dół kopca, zamieniając go z mniejszym\n        dzieckiem, dopóki nie zostanie przywrócona własność kopca.\n        \"\"\"\n        n = len(self._heap)\n        while True:\n            smallest = i\n            l, r = self._left(i), self._right(i)\n\n            # Szukamy najmniejszego spośród węzła i jego dzieci.\n            if l < n and self._heap[l][0] < self._heap[smallest][0]:\n                smallest = l\n            if r < n and self._heap[r][0] < self._heap[smallest][0]:\n                smallest = r\n\n            if smallest == i:\n                break  # własność kopca spełniona\n\n            self._swap(i, smallest)\n            i = smallest\n\n    # ------------------------------------------------------------------\n    # Interfejs publiczny\n    # ------------------------------------------------------------------\n\n    def push(self, key: Hashable, priority: Any) -> None:\n        \"\"\"\n        Dodaje element `key` z priorytetem `priority` do kolejki.\n\n        Args:\n            key: Unikalny, hashowalny identyfikator elementu.\n            priority: Priorytet (mniejszy = ważniejszy); musi być porównywalny.\n\n        Raises:\n            KeyError: jeśli klucz już istnieje w kolejce.\n        \"\"\"\n        if key in self._pos:\n            raise KeyError(f\"Klucz {key!r} już znajduje się w kolejce.\")\n\n        # Dodajemy na koniec tablicy i przesuwamy w górę.\n        self._heap.append([priority, key])\n        self._pos[key] = len(self._heap) - 1\n        self._sift_up(len(self._heap) - 1)\n\n    def pop(self) -> Tuple[Any, Hashable]:\n        \"\"\"\n        Usuwa i zwraca element o najmniejszym priorytecie.\n\n        Returns:\n            Krotka (priorytet, klucz).\n\n        Raises:\n            IndexError: jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"pop z pustej kolejki priorytetowej\")\n\n        top = self._heap[0]\n        last = self._heap.pop()  # zdejmujemy ostatni element\n        del self._pos[top[1]]\n\n        if self._heap:\n            # Ostatni element wstawiamy na szczyt i przesuwamy w dół.\n            self._heap[0] = last\n            self._pos[last[1]] = 0\n            self._sift_down(0)\n\n        return top[0], top[1]\n\n    def peek(self) -> Tuple[Any, Hashable]:\n        \"\"\"\n        Zwraca (bez usuwania) element o najmniejszym priorytecie.\n\n        Returns:\n            Krotka (priorytet, klucz).\n\n        Raises:\n            IndexError: jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"peek z pustej kolejki priorytetowej\")\n        priority, key = self._heap[0]\n        return priority, key\n\n    def decrease_key(self, key: Hashable, new_priority: Any) -> None:\n        \"\"\"\n        Zmniejsza priorytet istniejącego elementu i przywraca własność kopca.\n\n        Args:\n            key: Klucz elementu, którego priorytet ma zostać zmniejszony.\n            new_priority: Nowy priorytet; musi być <= aktualnego.\n\n        Raises:\n            KeyError: jeśli klucz nie istnieje w kolejce.\n            ValueError: jeśli nowy priorytet jest większy od obecnego.\n        \"\"\"\n        if key not in self._pos:\n            raise KeyError(f\"Klucz {key!r} nie istnieje w kolejce.\")\n\n        i = self._pos[key]\n        if new_priority > self._heap[i][0]:\n            raise ValueError(\n                f\"Nowy priorytet {new_priority!r} jest większy niż obecny \"\n                f\"{self._heap[i][0]!r}.\"\n            )\n\n        # Zmniejszenie priorytetu może naruszyć własność kopca tylko\n        # w kierunku rodzica, więc wystarczy przesunięcie w górę.\n        self._heap[i][0] = new_priority\n        self._sift_up(i)\n\n    def get_priority(self, key: Hashable) -> Optional[Any]:\n        \"\"\"Zwraca aktualny priorytet klucza lub None, jeśli klucza nie ma.\"\"\"\n        i = self._pos.get(key)\n        return None if i is None else self._heap[i][0]\n\n    # ------------------------------------------------------------------\n    # Metody specjalne\n    # ------------------------------------------------------------------\n\n    def __len__(self) -> int:\n        \"\"\"Zwraca liczbę elementów w kolejce.\"\"\"\n        return len(self._heap)\n\n    def __bool__(self) -> bool:\n        \"\"\"Kolejka jest 'prawdziwa', gdy zawiera elementy.\"\"\"\n        return bool(self._heap)\n\n    def __contains__(self, key: Hashable) -> bool:\n        \"\"\"Sprawdza, czy klucz znajduje się w kolejce.\"\"\"\n        return key in self._pos\n\n    def __repr__(self) -> str:\n        items = \", \".join(f\"({p!r}, {k!r})\" for p, k in self._heap)\n        return f\"PriorityQueue([{items}])\"\n\n\n# ----------------------------------------------------------------------\n# Przykład użycia / prosty test\n# ----------------------------------------------------------------------\nif __name__ == \"__main__\":\n    pq = PriorityQueue()\n    pq.push(\"A\", 5)\n    pq.push(\"B\", 3)\n    pq.push(\"C\", 8)\n    pq.push(\"D\", 1)\n\n    print(len(pq))          # 4\n    print(pq.peek())        # (1, 'D')\n\n    pq.decrease_key(\"C\", 0)\n    print(pq.peek())        # (0, 'C')\n\n    while pq:\n        print(pq.pop())     # (0,'C') (1,'D') (3,'B') (5,'A')\n```",
   "czas_s": 30.974,
   "usage": {
    "prompt_tokens": 92,
    "completion_tokens": 3040,
    "total_tokens": 3132,
    "cost": 0.15292,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.15292,
     "upstream_inference_prompt_cost": 0.00092,
     "upstream_inference_completions_cost": 0.152
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.15292,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.15292,
   "ttft_s": 2.208,
   "ttft_dowolny_token_s": 2.208,
   "gen_s": 28.765,
   "tok_s": 105.7,
   "slowa": 660,
   "slowa_na_s_calkowity_czas": 21.3,
   "tokeny": {
    "wej": 92,
    "wyj": 3040,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S3_tlumaczenie#2",
   "model": "anthropic/claude-fable-5.1",
   "provider": "Google",
   "prompt": "Przetłumacz na angielski, zachowując akapity:\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "On Wednesday, the Brzeziny Górne City Council adopted the \"Bike Around the Corner\" program. From 1 March 2027, the city is to have 14 city bike stations, including four at secondary schools and two at the railway station. A total of 160 bikes will be available for rental, 40 of them with electric assistance. The first 20 minutes of each ride will be free, and each subsequent hour or part thereof is to cost 3 złoty; for an electric bike, 5 złoty.\n\nThe program costs PLN 2.1 million over three years. Half will be covered by a grant from the provincial fund, the rest by the city budget. The operator will be a company selected through a tender, which is to be announced by the end of December. Opposition councillors voted against, pointing out that the city still lacks dedicated cycle paths: there are currently 11 km of them, and according to the plan there are to be 26 km by 2028. The mayor responds that the city bikes and the new routes will be developed in parallel, and the first 6 km of new paths will be opened before the rental system launches.\n\nResidents of the Zatorze housing estate, which will not get any station, also appeared at the session. Officials promised that after the first season they would analyze the rental data and consider expanding the network. A survey conducted in the summer showed that 38 percent of adult residents would like to use a city bike, most often on the way to work or to catch a train.",
   "czas_s": 5.891,
   "usage": {
    "prompt_tokens": 690,
    "completion_tokens": 447,
    "total_tokens": 1137,
    "cost": 0.02925,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.02925,
     "upstream_inference_prompt_cost": 0.0069,
     "upstream_inference_completions_cost": 0.02235
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.02925,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.02925,
   "ttft_s": 1.548,
   "ttft_dowolny_token_s": 1.548,
   "gen_s": 4.344,
   "tok_s": 102.9,
   "slowa": 261,
   "slowa_na_s_calkowity_czas": 44.3,
   "tokeny": {
    "wej": 690,
    "wyj": 447,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S1_esej#1",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Napisz po polsku tekst na około 500 słów o tym, jak działa kompostownik w ogrodzie. Bez wstępów i podsumowań typu „podsumowując”.",
   "odpowiedz": "Kompostownik to miejsce, w którym odpady organiczne z ogrodu i kuchni zamieniają się w żyzną, ciemną ziemię. Cały proces opiera się na pracy mikroorganizmów, grzybów, bakterii oraz drobnych zwierząt glebowych, takich jak dżdżownice, skoczogonki czy roztocza. To one rozkładają materię roślinną na prostsze związki, które później stają się pokarmem dla roślin.\n\nNajważniejszym warunkiem prawidłowego kompostowania jest odpowiednia mieszanka materiałów. Ogrodnicy dzielą je zwykle na dwie grupy: „zielone” i „brązowe”. Do pierwszej należą świeże resztki bogate w azot, czyli skoszona trawa, obierki warzyw i owoców, fusy z kawy, liście sałaty czy chwasty bez nasion. Druga grupa to materiały suche i bogate w węgiel: opadłe liście, słoma, rozdrobnione gałązki, tektura, kartony po jajkach. Mikroorganizmy potrzebują obu pierwiastków. Węgiel stanowi dla nich źródło energii, a azot służy do budowy białek. Gdy w pryzmie przeważa materiał zielony, kompost zaczyna gnić i wydzielać nieprzyjemny zapach. Nadmiar brązowego spowalnia z kolei rozkład niemal do zera.\n\nRównie ważny jest dostęp powietrza. Bakterie tlenowe, które odpowiadają za szybki i bezwonny rozkład, potrzebują tlenu. Dlatego kompostownik powinien mieć szczeliny w ściankach, a jego zawartość warto co kilka tygodni przerzucać widłami. Przerzucanie miesza warstwy, napowietrza pryzmę i przyspiesza cały proces. Gdy tlenu brakuje, przewagę zdobywają bakterie beztlenowe, a wtedy pojawia się zapach zgnilizny i amoniaku.\n\nKolejnym czynnikiem jest wilgotność. Materiał w kompostowniku powinien przypominać wyciśniętą gąbkę, czyli być wilgotny, ale nie mokry. W czasie suszy pryzmę trzeba podlać, a podczas długotrwałych deszczów przykryć, na przykład deskami, matą lub kawałkiem tektury.\n\nW dobrze prowadzonym kompostowniku zachodzą kolejne fazy rozkładu. Na początku namnażają się bakterie, które szybko rozkładają łatwo dostępne cukry i białka. Temperatura wewnątrz pryzmy rośnie wtedy nawet do 60–70°C. Ta faza gorąca niszczy wiele nasion chwastów i drobnoustrojów chorobotwórczych. Po kilku tygodniach temperatura spada, a do pracy przystępują grzyby i promieniowce, które radzą sobie z trudniejszymi związkami, takimi jak celuloza i lignina. W ostatniej fazie, zwanej dojrzewaniem, do kompostu wchodzą dżdżownice i inne organizmy glebowe. Przerabiają one masę na próchnicę o gruzełkowatej strukturze.\n\nLokalizacja kompostownika także ma znaczenie. Najlepiej ustawić go w półcieniu, na przykład pod drzewem, bezpośrednio na gruncie, a nie na betonie. Kontakt z ziemią ułatwia organizmom glebowym przedostanie się do wnętrza i zapewnia odpływ nadmiaru wody.\n\nNie wszystko nadaje się do kompostowania. Należy unikać mięsa, kości, nabiału i tłuszczów, ponieważ przyciągają szczury i wydzielają przykry zapach. Do kompostownika nie powinny też trafiać chore rośliny, odchody psów i kotów, popiół z węgla ani kolorowy papier kredowy.\n\nDojrzały kompost ma ciemnobrązowy kolor i pachnie leśną ściółką. W jego masie nie da się już rozpoznać pierwotnych składników. W zależności od warunków i staranności pielęgnacji powstaje po kilku miesiącach, a czasem dopiero po roku lub dwóch. Przed użyciem warto go przesiać, a większe, nierozłożone kawałki odłożyć z powrotem do kompostownika. Gotowy kompost można rozsypywać pod drzewa i krzewy, dodawać do grządek warzywnych oraz mieszać z ziemią do sadzenia roślin doniczkowych. Poprawia on strukturę gleby, zwiększa jej zdolność do zatrzymywania wody i dostarcza roślinom składników odżywczych.",
   "czas_s": 21.767,
   "usage": {
    "prompt_tokens": 70,
    "completion_tokens": 1710,
    "total_tokens": 1780,
    "cost": 0.03448,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.03448,
     "upstream_inference_prompt_cost": 0.00028,
     "upstream_inference_completions_cost": 0.0342
    },
    "completion_tokens_details": {
     "reasoning_tokens": 28,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.03448,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.03448,
   "ttft_s": 2.013,
   "ttft_dowolny_token_s": 2.007,
   "gen_s": 19.76,
   "tok_s": 86.5,
   "slowa": 484,
   "slowa_na_s_calkowity_czas": 22.2,
   "tokeny": {
    "wej": 70,
    "wyj": 1710,
    "rozumowanie": 28
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S2_kod#1",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Napisz w Pythonie kompletną implementację kolejki priorytetowej opartej na kopcu binarnym (klasa z metodami push, pop, peek, __len__, decrease_key) z docstringami i komentarzami. Tylko kod.",
   "odpowiedz": "```python\n\"\"\"\nModuł implementujący kolejkę priorytetową opartą na kopcu binarnym (min-kopiec).\n\nElementy o mniejszym priorytecie są obsługiwane wcześniej.\nKolejka obsługuje operację decrease_key dzięki słownikowi pozycji,\nktóry mapuje każdy element na jego indeks w tablicy kopca.\n\"\"\"\n\nfrom typing import Any, Dict, Generic, Hashable, List, Tuple, TypeVar\n\nT = TypeVar(\"T\", bound=Hashable)\n\n\nclass PriorityQueue(Generic[T]):\n    \"\"\"\n    Kolejka priorytetowa typu min oparta na kopcu binarnym.\n\n    Każdy element musi być hashowalny i unikalny w obrębie kolejki,\n    ponieważ jest wykorzystywany jako klucz w słowniku pozycji.\n\n    Złożoności czasowe:\n        push          - O(log n)\n        pop           - O(log n)\n        peek          - O(1)\n        decrease_key  - O(log n)\n        __len__       - O(1)\n        __contains__  - O(1)\n    \"\"\"\n\n    def __init__(self) -> None:\n        \"\"\"Tworzy pustą kolejkę priorytetową.\"\"\"\n        # Tablica kopca: lista par (priorytet, element)\n        self._heap: List[Tuple[Any, T]] = []\n        # Mapowanie element -> indeks w tablicy kopca\n        self._position: Dict[T, int] = {}\n\n    # ------------------------------------------------------------------\n    # Metody publiczne\n    # ------------------------------------------------------------------\n\n    def push(self, item: T, priority: Any) -> None:\n        \"\"\"\n        Dodaje element o podanym priorytecie do kolejki.\n\n        Args:\n            item: Element do dodania (hashowalny, unikalny).\n            priority: Priorytet elementu (porównywalny z innymi priorytetami).\n\n        Raises:\n            ValueError: Jeśli element już znajduje się w kolejce.\n        \"\"\"\n        if item in self._position:\n            raise ValueError(f\"Element {item!r} już znajduje się w kolejce\")\n\n        # Dodajemy element na koniec tablicy i przywracamy własność kopca w górę\n        self._heap.append((priority, item))\n        index = len(self._heap) - 1\n        self._position[item] = index\n        self._sift_up(index)\n\n    def pop(self) -> Tuple[T, Any]:\n        \"\"\"\n        Usuwa i zwraca element o najmniejszym priorytecie.\n\n        Returns:\n            Krotka (element, priorytet).\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"pop z pustej kolejki priorytetowej\")\n\n        # Zamieniamy korzeń z ostatnim elementem, aby usunąć go w O(1)\n        last_index = len(self._heap) - 1\n        self._swap(0, last_index)\n        priority, item = self._heap.pop()\n        del self._position[item]\n\n        # Jeśli coś zostało, przywracamy własność kopca w dół od korzenia\n        if self._heap:\n            self._sift_down(0)\n\n        return item, priority\n\n    def peek(self) -> Tuple[T, Any]:\n        \"\"\"\n        Zwraca (bez usuwania) element o najmniejszym priorytecie.\n\n        Returns:\n            Krotka (element, priorytet).\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"peek z pustej kolejki priorytetowej\")\n        priority, item = self._heap[0]\n        return item, priority\n\n    def decrease_key(self, item: T, new_priority: Any) -> None:\n        \"\"\"\n        Zmniejsza priorytet istniejącego elementu.\n\n        Args:\n            item: Element, którego priorytet ma zostać zmniejszony.\n            new_priority: Nowy priorytet (nie większy niż obecny).\n\n        Raises:\n            KeyError: Jeśli elementu nie ma w kolejce.\n            ValueError: Jeśli nowy priorytet jest większy od obecnego.\n        \"\"\"\n        if item not in self._position:\n            raise KeyError(f\"Elementu {item!r} nie ma w kolejce\")\n\n        index = self._position[item]\n        current_priority, _ = self._heap[index]\n\n        if new_priority > current_priority:\n            raise ValueError(\n                f\"Nowy priorytet {new_priority!r} jest większy \"\n                f\"od obecnego {current_priority!r}\"\n            )\n\n        # Zmniejszenie priorytetu może naruszyć własność kopca tylko w górę\n        self._heap[index] = (new_priority, item)\n        self._sift_up(index)\n\n    def __len__(self) -> int:\n        \"\"\"Zwraca liczbę elementów w kolejce.\"\"\"\n        return len(self._heap)\n\n    def __contains__(self, item: object) -> bool:\n        \"\"\"Sprawdza, czy element znajduje się w kolejce.\"\"\"\n        return item in self._position\n\n    def __bool__(self) -> bool:\n        \"\"\"Zwraca True, jeśli kolejka nie jest pusta.\"\"\"\n        return bool(self._heap)\n\n    def __repr__(self) -> str:\n        \"\"\"Zwraca tekstową reprezentację kolejki (kolejność kopca).\"\"\"\n        content = \", \".join(f\"({item!r}, {prio!r})\" for prio, item in self._heap)\n        return f\"{self.__class__.__name__}([{content}])\"\n\n    # ------------------------------------------------------------------\n    # Metody pomocnicze (prywatne)\n    # ------------------------------------------------------------------\n\n    @staticmethod\n    def _parent(index: int) -> int:\n        \"\"\"Zwraca indeks rodzica węzła.\"\"\"\n        return (index - 1) // 2\n\n    @staticmethod\n    def _left(index: int) -> int:\n        \"\"\"Zwraca indeks lewego dziecka węzła.\"\"\"\n        return 2 * index + 1\n\n    @staticmethod\n    def _right(index: int) -> int:\n        \"\"\"Zwraca indeks prawego dziecka węzła.\"\"\"\n        return 2 * index + 2\n\n    def _less(self, i: int, j: int) -> bool:\n        \"\"\"\n        Porównuje priorytety elementów na pozycjach i oraz j.\n\n        Porównujemy wyłącznie priorytety, aby nie wymagać\n        porównywalności samych elementów.\n        \"\"\"\n        return self._heap[i][0] < self._heap[j][0]\n\n    def _swap(self, i: int, j: int) -> None:\n        \"\"\"Zamienia miejscami elementy i aktualizuje słownik pozycji.\"\"\"\n        self._heap[i], self._heap[j] = self._heap[j], self._heap[i]\n        self._position[self._heap[i][1]] = i\n        self._position[self._heap[j][1]] = j\n\n    def _sift_up(self, index: int) -> None:\n        \"\"\"\n        Przesuwa element w górę kopca, dopóki jego priorytet\n        jest mniejszy niż priorytet rodzica.\n        \"\"\"\n        while index > 0:\n            parent = self._parent(index)\n            if self._less(index, parent):\n                self._swap(index, parent)\n                index = parent\n            else:\n                break\n\n    def _sift_down(self, index: int) -> None:\n        \"\"\"\n        Przesuwa element w dół kopca, zamieniając go z mniejszym\n        z dzieci, dopóki własność kopca nie zostanie przywrócona.\n        \"\"\"\n        size = len(self._heap)\n        while True:\n            left = self._left(index)\n            right = self._right(index)\n            smallest = index\n\n            # Wybieramy najmniejszy spośród węzła i jego dzieci\n            if left < size and self._less(left, smallest):\n                smallest = left\n            if right < size and self._less(right, smallest):\n                smallest = right\n\n            if smallest == index:\n                break\n\n            self._swap(index, smallest)\n            index = smallest\n\n\nif __name__ == \"__main__\":\n    # Prosta demonstracja działania\n    pq: PriorityQueue[str] = PriorityQueue()\n    pq.push(\"A\", 5)\n    pq.push(\"B\", 3)\n    pq.push(\"C\", 8)\n    pq.push(\"D\", 1)\n\n    print(pq)                 # Stan kopca\n    print(pq.peek())          # ('D', 1)\n\n    pq.decrease_key(\"C\", 0)   # C staje się najważniejszy\n    print(pq.peek())          # ('C', 0)\n\n    while pq:\n        print(pq.pop())       # C, D, B, A\n```",
   "czas_s": 22.367,
   "usage": {
    "prompt_tokens": 92,
    "completion_tokens": 2969,
    "total_tokens": 3061,
    "cost": 0.059748,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.059748,
     "upstream_inference_prompt_cost": 0.000368,
     "upstream_inference_completions_cost": 0.05938
    },
    "completion_tokens_details": {
     "reasoning_tokens": 90,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.059748,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.059748,
   "ttft_s": 2.909,
   "ttft_dowolny_token_s": 2.895,
   "gen_s": 19.471,
   "tok_s": 152.5,
   "slowa": 658,
   "slowa_na_s_calkowity_czas": 29.4,
   "tokeny": {
    "wej": 92,
    "wyj": 2969,
    "rozumowanie": 90
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S3_tlumaczenie#1",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Przetłumacz na angielski, zachowując akapity:\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "On Wednesday, the Brzeziny Górne City Council adopted the \"Bike Around the Corner\" programme. From 1 March 2027, the town is to have 14 city bike stations, including four at secondary schools and two at the railway station. A total of 160 bikes will be available for hire, 40 of them electrically assisted. The first 20 minutes of every ride will be free, and each additional hour or part thereof is to cost PLN 3, or PLN 5 for an e-bike.\n\nThe programme will cost PLN 2.1 million over three years. Half will be covered by a grant from a regional fund and the rest by the town's budget. The operator will be a company selected through a tender, which is to be announced by the end of December. Opposition councillors voted against, pointing out that the town still lacks dedicated cycle paths: there are currently 11 km of them, and the plan calls for 26 km by 2028. The mayor responded that the city bikes and the new routes will be developed in parallel, and that the first 6 km of new paths will be completed before the bike hire scheme launches.\n\nResidents of the Zatorze neighbourhood, which will not get a single station, also attended the session. Officials promised that after the first season they would analyse the rental data and consider expanding the network. A survey conducted in the summer showed that 38 percent of adult residents would like to use the city bikes, most often to get to work or to catch a train.",
   "czas_s": 5.972,
   "usage": {
    "prompt_tokens": 690,
    "completion_tokens": 519,
    "total_tokens": 1209,
    "cost": 0.01314,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.01314,
     "upstream_inference_prompt_cost": 0.00276,
     "upstream_inference_completions_cost": 0.01038
    },
    "completion_tokens_details": {
     "reasoning_tokens": 69,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.01314,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.01314,
   "ttft_s": 4.12,
   "ttft_dowolny_token_s": 4.11,
   "gen_s": 1.862,
   "tok_s": 278.7,
   "slowa": 258,
   "slowa_na_s_calkowity_czas": 43.2,
   "tokeny": {
    "wej": 690,
    "wyj": 519,
    "rozumowanie": 69
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S1_esej#2",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Napisz po polsku tekst na około 500 słów o tym, jak działa kompostownik w ogrodzie. Bez wstępów i podsumowań typu „podsumowując”.",
   "odpowiedz": "Kompostownik to pojemnik lub wydzielone miejsce, w którym odpady organiczne zamieniają się w żyzną ziemię. Cały proces opiera się na pracy mikroorganizmów, grzybów, dżdżownic i innych drobnych organizmów glebowych, które rozkładają materię roślinną na prostsze związki. Efektem ich działalności jest kompost, czyli ciemna, pachnąca lasem substancja bogata w próchnicę i składniki odżywcze.\n\nKluczem do sprawnego działania kompostownika jest odpowiednia proporcja materiałów. Ogrodnicy dzielą je zazwyczaj na „zielone” i „brązowe”. Do pierwszej grupy należą świeże resztki roślinne: skoszona trawa, obierki warzyw i owoców, fusy z kawy, liście herbaty czy chwasty bez nasion. Są one bogate w azot, który stanowi pożywienie dla bakterii. Materiały brązowe to suche liście, gałązki, słoma, trociny czy kawałki tektury. Dostarczają węgla i nadają pryzmie strukturę, dzięki której do środka dociera powietrze. Najlepsze rezultaty daje mieszanka, w której materiałów brązowych jest mniej więcej dwa, trzy razy więcej niż zielonych.\n\nRównie ważny jest dostęp tlenu. Bakterie tlenowe pracują najszybciej i nie wytwarzają przykrego zapachu. Gdy w pryzmie brakuje powietrza, przewagę zdobywają organizmy beztlenowe, a kompost zaczyna gnić i wydzielać woń siarkowodoru. Dlatego warto co kilka tygodni przerzucić zawartość widłami lub specjalnym napowietrzaczem. Pomaga też układanie na dnie warstwy grubszych gałęzi, które tworzą kanały powietrzne.\n\nWilgotność powinna przypominać wyciśniętą gąbkę. Zbyt sucha pryzma praktycznie przestaje się rozkładać, bo mikroorganizmy potrzebują wody do życia. Zbyt mokra robi się zbita i pozbawiona tlenu. W upalne lato kompost można podlać, a podczas długotrwałych deszczów przykryć matą, deskami lub folią z otworami.\n\nW pierwszej fazie rozkładu temperatura wewnątrz pryzmy wyraźnie rośnie i może przekroczyć 60 stopni Celsjusza. To znak intensywnej pracy bakterii termofilnych. Wysoka temperatura ma dodatkową zaletę, ponieważ niszczy wiele nasion chwastów i patogenów roślinnych. Po kilku tygodniach proces zwalnia, pryzma stygnie, a do akcji wkraczają dżdżownice, skoczogonki i grzyby, które kontynuują rozkład i nadają kompostowi gruzełkowatą strukturę.\n\nNie wszystko nadaje się do kompostownika. Należy unikać mięsa, kości, nabiału i tłuszczów, które przyciągają szczury i lisy oraz psują zapach. Nie wrzuca się też chorych roślin, odchodów psów i kotów, popiołu z węgla ani materiałów zadrukowanych kolorowym tuszem. Ostrożnie trzeba podchodzić do skórek cytrusów i liści orzecha włoskiego, które rozkładają się wolno i mogą zakwaszać masę.\n\nPojemnik najlepiej ustawić w półcieniu, bezpośrednio na gruncie, aby organizmy glebowe mogły swobodnie migrować do środka. Popularne są skrzynie drewniane z deskami rozstawionymi w odstępach, plastikowe kompostowniki z pokrywą oraz modele obrotowe, które ułatwiają mieszanie. Dobrym rozwiązaniem jest posiadanie dwóch lub trzech komór: do jednej dokłada się świeże odpady, w drugiej kompost dojrzewa.\n\nDojrzały kompost uzyskuje się zwykle po sześciu do dwunastu miesięcy, zależnie od składu i pielęgnacji. Rozpoznaje się go po ciemnej barwie, sypkiej konsystencji i zapachu leśnej ściółki. Można go przesiać i używać do nawożenia grządek, ściółkowania drzew, przygotowania podłoża do rozsad czy odświeżania trawnika.",
   "czas_s": 21.781,
   "usage": {
    "prompt_tokens": 70,
    "completion_tokens": 1633,
    "total_tokens": 1703,
    "cost": 0.03294,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.03294,
     "upstream_inference_prompt_cost": 0.00028,
     "upstream_inference_completions_cost": 0.03266
    },
    "completion_tokens_details": {
     "reasoning_tokens": 49,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.03294,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.03294,
   "ttft_s": 2.508,
   "ttft_dowolny_token_s": 2.5,
   "gen_s": 19.282,
   "tok_s": 84.7,
   "slowa": 449,
   "slowa_na_s_calkowity_czas": 20.6,
   "tokeny": {
    "wej": 70,
    "wyj": 1633,
    "rozumowanie": 49
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S2_kod#2",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Napisz w Pythonie kompletną implementację kolejki priorytetowej opartej na kopcu binarnym (klasa z metodami push, pop, peek, __len__, decrease_key) z docstringami i komentarzami. Tylko kod.",
   "odpowiedz": "```python\n\"\"\"\nModuł implementujący kolejkę priorytetową opartą na kopcu binarnym (min-heap).\n\nKolejka obsługuje operację decrease_key, dzięki czemu nadaje się m.in.\ndo algorytmów Dijkstry i Prima. Elementy muszą być hashowalne i unikalne.\n\"\"\"\n\nfrom typing import Any, Dict, Generic, Hashable, List, Tuple, TypeVar\n\nT = TypeVar(\"T\", bound=Hashable)\n\n\nclass PriorityQueue(Generic[T]):\n    \"\"\"\n    Kolejka priorytetowa typu min oparta na kopcu binarnym.\n\n    Element o najmniejszym priorytecie znajduje się zawsze na szczycie kopca.\n    Dodatkowy słownik pozycji pozwala w czasie O(1) odnaleźć element w kopcu,\n    co umożliwia wykonanie decrease_key w czasie O(log n).\n\n    Złożoności:\n        push         - O(log n)\n        pop          - O(log n)\n        peek         - O(1)\n        decrease_key - O(log n)\n        __len__      - O(1)\n        __contains__ - O(1)\n    \"\"\"\n\n    def __init__(self) -> None:\n        \"\"\"Tworzy pustą kolejkę priorytetową.\"\"\"\n        # Kopiec przechowywany jako lista par (priorytet, element).\n        self._heap: List[Tuple[Any, T]] = []\n        # Mapowanie element -> indeks w liście _heap.\n        self._position: Dict[T, int] = {}\n\n    # ------------------------------------------------------------------\n    # Metody publiczne\n    # ------------------------------------------------------------------\n\n    def push(self, item: T, priority: Any) -> None:\n        \"\"\"\n        Dodaje element z podanym priorytetem do kolejki.\n\n        Args:\n            item: Element (hashowalny, unikalny w kolejce).\n            priority: Priorytet elementu (mniejszy = ważniejszy).\n\n        Raises:\n            ValueError: Jeśli element już znajduje się w kolejce.\n        \"\"\"\n        if item in self._position:\n            raise ValueError(f\"Element {item!r} już znajduje się w kolejce.\")\n\n        # Wstawiamy na koniec kopca i przywracamy własność kopca w górę.\n        self._heap.append((priority, item))\n        index = len(self._heap) - 1\n        self._position[item] = index\n        self._sift_up(index)\n\n    def pop(self) -> Tuple[T, Any]:\n        \"\"\"\n        Usuwa i zwraca element o najmniejszym priorytecie.\n\n        Returns:\n            Krotka (element, priorytet).\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"pop z pustej kolejki priorytetowej.\")\n\n        # Zamieniamy korzeń z ostatnim elementem, aby usunąć go w O(1).\n        last_index = len(self._heap) - 1\n        self._swap(0, last_index)\n        priority, item = self._heap.pop()\n        del self._position[item]\n\n        # Jeśli coś zostało, przywracamy własność kopca w dół od korzenia.\n        if self._heap:\n            self._sift_down(0)\n\n        return item, priority\n\n    def peek(self) -> Tuple[T, Any]:\n        \"\"\"\n        Zwraca (bez usuwania) element o najmniejszym priorytecie.\n\n        Returns:\n            Krotka (element, priorytet).\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"peek z pustej kolejki priorytetowej.\")\n        priority, item = self._heap[0]\n        return item, priority\n\n    def decrease_key(self, item: T, new_priority: Any) -> None:\n        \"\"\"\n        Zmniejsza priorytet istniejącego elementu.\n\n        Args:\n            item: Element obecny w kolejce.\n            new_priority: Nowy priorytet (nie większy niż obecny).\n\n        Raises:\n            KeyError: Jeśli elementu nie ma w kolejce.\n            ValueError: Jeśli nowy priorytet jest większy niż obecny.\n        \"\"\"\n        if item not in self._position:\n            raise KeyError(f\"Elementu {item!r} nie ma w kolejce.\")\n\n        index = self._position[item]\n        current_priority, _ = self._heap[index]\n\n        if new_priority > current_priority:\n            raise ValueError(\n                f\"Nowy priorytet {new_priority!r} jest większy niż \"\n                f\"obecny {current_priority!r}.\"\n            )\n\n        # Mniejszy priorytet może naruszyć własność kopca tylko w górę.\n        self._heap[index] = (new_priority, item)\n        self._sift_up(index)\n\n    def __len__(self) -> int:\n        \"\"\"Zwraca liczbę elementów w kolejce.\"\"\"\n        return len(self._heap)\n\n    def __bool__(self) -> bool:\n        \"\"\"Zwraca True, jeśli kolejka nie jest pusta.\"\"\"\n        return bool(self._heap)\n\n    def __contains__(self, item: object) -> bool:\n        \"\"\"Sprawdza w O(1), czy element znajduje się w kolejce.\"\"\"\n        return item in self._position\n\n    def __repr__(self) -> str:\n        \"\"\"Zwraca tekstową reprezentację kolejki (w kolejności kopca).\"\"\"\n        return f\"{self.__class__.__name__}({self._heap!r})\"\n\n    # ------------------------------------------------------------------\n    # Metody pomocnicze (prywatne)\n    # ------------------------------------------------------------------\n\n    @staticmethod\n    def _parent(index: int) -> int:\n        \"\"\"Zwraca indeks rodzica węzła.\"\"\"\n        return (index - 1) // 2\n\n    @staticmethod\n    def _left(index: int) -> int:\n        \"\"\"Zwraca indeks lewego dziecka węzła.\"\"\"\n        return 2 * index + 1\n\n    @staticmethod\n    def _right(index: int) -> int:\n        \"\"\"Zwraca indeks prawego dziecka węzła.\"\"\"\n        return 2 * index + 2\n\n    def _less(self, i: int, j: int) -> bool:\n        \"\"\"Porównuje wyłącznie priorytety (elementy nie muszą być porównywalne).\"\"\"\n        return self._heap[i][0] < self._heap[j][0]\n\n    def _swap(self, i: int, j: int) -> None:\n        \"\"\"Zamienia miejscami dwa węzły i aktualizuje słownik pozycji.\"\"\"\n        self._heap[i], self._heap[j] = self._heap[j], self._heap[i]\n        self._position[self._heap[i][1]] = i\n        self._position[self._heap[j][1]] = j\n\n    def _sift_up(self, index: int) -> None:\n        \"\"\"Przesuwa węzeł w górę, dopóki jest mniejszy od rodzica.\"\"\"\n        while index > 0:\n            parent = self._parent(index)\n            if self._less(index, parent):\n                self._swap(index, parent)\n                index = parent\n            else:\n                break\n\n    def _sift_down(self, index: int) -> None:\n        \"\"\"Przesuwa węzeł w dół, zamieniając go z mniejszym z dzieci.\"\"\"\n        size = len(self._heap)\n        while True:\n            left = self._left(index)\n            right = self._right(index)\n            smallest = index\n\n            if left < size and self._less(left, smallest):\n                smallest = left\n            if right < size and self._less(right, smallest):\n                smallest = right\n\n            if smallest == index:\n                break  # Własność kopca przywrócona.\n\n            self._swap(index, smallest)\n            index = smallest\n\n\nif __name__ == \"__main__\":\n    # Prosta demonstracja działania.\n    pq: PriorityQueue[str] = PriorityQueue()\n    pq.push(\"A\", 5)\n    pq.push(\"B\", 3)\n    pq.push(\"C\", 8)\n    pq.push(\"D\", 1)\n\n    print(\"Rozmiar:\", len(pq))          # 4\n    print(\"Szczyt:\", pq.peek())         # ('D', 1)\n\n    pq.decrease_key(\"C\", 0)\n    print(\"Po decrease_key:\", pq.peek())  # ('C', 0)\n\n    while pq:\n        print(pq.pop())\n    # ('C', 0), ('D', 1), ('B', 3), ('A', 5)\n```",
   "czas_s": 21.53,
   "usage": {
    "prompt_tokens": 92,
    "completion_tokens": 2874,
    "total_tokens": 2966,
    "cost": 0.057848,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.057848,
     "upstream_inference_prompt_cost": 0.000368,
     "upstream_inference_completions_cost": 0.05748
    },
    "completion_tokens_details": {
     "reasoning_tokens": 67,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.057848,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.057848,
   "ttft_s": 2.793,
   "ttft_dowolny_token_s": 2.789,
   "gen_s": 18.741,
   "tok_s": 153.4,
   "slowa": 635,
   "slowa_na_s_calkowity_czas": 29.5,
   "tokeny": {
    "wej": 92,
    "wyj": 2874,
    "rozumowanie": 67
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S3_tlumaczenie#2",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Przetłumacz na angielski, zachowując akapity:\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "The Brzeziny Górne City Council adopted the \"Bike Around the Corner\" programme on Wednesday. From 1 March 2027, the town is to have 14 city bike stations, including four at secondary schools and two at the railway station. A total of 160 bikes will be available for hire, 40 of them electric-assisted. The first 20 minutes of each ride will be free, and each additional hour or part thereof will cost 3 zlotys, or 5 zlotys for an electric bike.\n\nThe programme will cost PLN 2.1 million over three years. Half will be covered by a grant from the regional fund and the rest by the town budget. The operator will be a company selected through a tender, which is due to be announced by the end of December. Opposition councillors voted against, pointing out that the town still lacks dedicated cycle paths: there are currently 11 km of them, and the plan calls for 26 km by 2028. The mayor responded that the city bikes and the new routes will be developed in parallel, and that the first 6 km of new paths will be completed before the bike-share scheme launches.\n\nResidents of the Zatorze housing estate, which will not get a single station, also attended the session. Officials promised that after the first season they would analyse the rental data and consider expanding the network. A survey conducted in the summer showed that 38 percent of adult residents would like to use the city bikes, most often to get to work or to catch a train.",
   "czas_s": 5.371,
   "usage": {
    "prompt_tokens": 690,
    "completion_tokens": 449,
    "total_tokens": 1139,
    "cost": 0.01174,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.01174,
     "upstream_inference_prompt_cost": 0.00276,
     "upstream_inference_completions_cost": 0.00898
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.01174,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.01174,
   "ttft_s": 1.692,
   "ttft_dowolny_token_s": 1.692,
   "gen_s": 3.68,
   "tok_s": 122.0,
   "slowa": 258,
   "slowa_na_s_calkowity_czas": 48.0,
   "tokeny": {
    "wej": 690,
    "wyj": 449,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S1_esej#1",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Napisz po polsku tekst na około 500 słów o tym, jak działa kompostownik w ogrodzie. Bez wstępów i podsumowań typu „podsumowując”.",
   "odpowiedz": "Kompostownik w ogrodzie to miejsce, w którym resztki organiczne stopniowo zamieniają się w ciemny, sypki materiał poprawiający żyzność i strukturę gleby. Proces ten zachodzi dzięki bakteriom, grzybom oraz drobnym organizmom, takim jak roztocze, skoczogonki i dżdżownice. Rozkładają one szczątki roślinne na prostsze związki, wykorzystując je jako źródło energii i składników potrzebnych do życia.\n\nAby kompostowanie przebiegało sprawnie, wewnątrz pryzmy muszą panować odpowiednie warunki: dostęp powietrza, umiarkowana wilgotność i właściwe proporcje materiałów. Kompostownik najlepiej ustawić w miejscu lekko zacienionym, osłoniętym od silnego wiatru, bezpośrednio na ziemi. Kontakt z podłożem ułatwia odpływ nadmiaru wody oraz przemieszczanie się organizmów glebowych. Na dnie warto ułożyć drobne gałązki albo grubsze, pocięte łodygi, które ograniczą zbijanie się dolnej warstwy.\n\nDo kompostownika trafiają dwa podstawowe rodzaje materiałów. Pierwsze to składniki „zielone”, bogate w azot: świeżo skoszona trawa, obierki warzyw i owoców, fusy z kawy oraz młode chwasty bez nasion. Drugie to materiały „brązowe”, zawierające dużo węgla: suche liście, słoma, rozdrobnione gałązki i niezadrukowana tektura bez powłok. Mikroorganizmy potrzebują obu grup. Zielone resztki dostarczają azotu, natomiast brązowe zapewniają energię i pomagają zachować przewiewną strukturę pryzmy.\n\nW praktyce warto mieszać mniej więcej dwie lub trzy części materiałów brązowych z jedną częścią zielonych, licząc objętościowo. Nie jest to jednak sztywna recepta, ponieważ poszczególne odpady różnią się składem i wilgotnością. Gruba warstwa samej trawy łatwo się zbija, odcina dopływ tlenu i zaczyna nieprzyjemnie pachnieć. Z kolei stos złożony głównie z suchych liści i gałęzi rozkłada się powoli, zwłaszcza gdy brakuje mu wilgoci.\n\nW dobrze pracującym kompoście mikroorganizmy zużywają tlen i wydzielają ciepło. W dostatecznie dużej, prawidłowo przygotowanej pryzmie temperatura może wzrosnąć do około 50–65°C. Taka gorąca faza przyspiesza rozkład i może ograniczać liczbę nasion chwastów oraz części drobnoustrojów chorobotwórczych. Mały kompostownik ogrodowy nie zawsze nagrzewa się tak mocno, ale nadal działa — przemiana zachodzi wtedy wolniej. Po okresie intensywnego rozkładu masa stopniowo stygnie, a większą rolę zaczynają odgrywać grzyby i drobne zwierzęta.\n\nWilgotność kompostu powinna przypominać dobrze wyciśniętą gąbkę. Jeśli materiał jest suchy i prawie się nie zmienia, należy go lekko podlać, najlepiej podczas mieszania. Gdy jest mokry, śliski i wydziela kwaśny lub gnilny zapach, trzeba dodać suche liście, rozdrobnioną tekturę albo słomę. Przerzucanie zawartości widłami napowietrza ją, rozluźnia zbite fragmenty i przenosi mniej rozłożone resztki z brzegów do środka. Można robić to co kilka tygodni, choć rzadsze mieszanie również pozwala uzyskać kompost, tylko po dłuższym czasie.\n\nDo zwykłego kompostownika ogrodowego nie należy wrzucać mięsa, ryb, kości, tłuszczu ani dużych ilości nabiału, ponieważ przyciągają zwierzęta i sprzyjają powstawaniu przykrych zapachów. Niewskazane są również odchody psów i kotów, drewno impregnowane, popiół z węgla oraz rośliny wyraźnie porażone chorobami. Resztki warto rozdrabniać, bo mniejsze kawałki mają większą powierzchnię dostępną dla organizmów rozkładających materię.\n\nDojrzewanie kompostu trwa zazwyczaj od kilku miesięcy do ponad roku. Gotowy materiał pachnie leśną ziemią, ma ciemnobrązową barwę i gruzełkowatą strukturę, a większości pierwotnych składników nie da się już rozpoznać. Można rozłożyć go cienką warstwą na rabatach, wymieszać z wierzchnią warstwą gleby albo wykorzystać jako dodatek do podłoża. Niedojrzały kompost lepiej pozostawić do dalszego rozkładu, ponieważ może czasowo ograniczać dostępność azotu i szkodzić delikatnym korzeniom.",
   "czas_s": 25.659,
   "usage": {
    "prompt_tokens": 49,
    "completion_tokens": 1195,
    "total_tokens": 1244,
    "cost": 0.012048,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.012048,
     "upstream_inference_prompt_cost": 9.8e-05,
     "upstream_inference_completions_cost": 0.01195
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.012048,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.012048,
   "ttft_s": 1.312,
   "ttft_dowolny_token_s": 1.312,
   "gen_s": 24.347,
   "tok_s": 49.1,
   "slowa": 504,
   "slowa_na_s_calkowity_czas": 19.6,
   "tokeny": {
    "wej": 49,
    "wyj": 1195,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S2_kod#1",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Napisz w Pythonie kompletną implementację kolejki priorytetowej opartej na kopcu binarnym (klasa z metodami push, pop, peek, __len__, decrease_key) z docstringami i komentarzami. Tylko kod.",
   "odpowiedz": "from dataclasses import dataclass\nfrom typing import Dict, Generic, List, Tuple, TypeVar\n\nT = TypeVar(\"T\")  # Typ przechowywanej wartości.\nP = TypeVar(\"P\")  # Typ priorytetu, obsługujący spójny porządek przez operator <.\n\n\n@dataclass\nclass _Entry(Generic[T, P]):\n    \"\"\"Wewnętrzny element kopca.\"\"\"\n\n    value: T\n    priority: P\n    handle: int\n\n\nclass PriorityQueue(Generic[T, P]):\n    \"\"\"Stabilna kolejka priorytetowa oparta na binarnym kopcu minimalnym.\n\n    Mniejszy priorytet oznacza wcześniejsze pobranie. Elementy o równych\n    priorytetach są pobierane w kolejności dodania.\n\n    Metoda push zwraca unikalny uchwyt wykorzystywany przez decrease_key.\n    Wartości nie muszą być porównywalne ani hashowalne.\n\n    Priorytety muszą tworzyć spójny porządek i nie mogą być modyfikowane\n    z zewnątrz po dodaniu do kolejki. W szczególności nie należy używać NaN.\n\n    Złożoność:\n        push, pop, decrease_key: O(log n).\n        peek, __len__: O(1).\n        Pamięć: O(n).\n    \"\"\"\n\n    def __init__(self) -> None:\n        \"\"\"Utwórz pustą kolejkę.\"\"\"\n        self._heap: List[_Entry[T, P]] = []\n        self._positions: Dict[int, int] = {}\n        self._next_handle = 0\n\n    def __len__(self) -> int:\n        \"\"\"Zwróć liczbę elementów kolejki.\"\"\"\n        return len(self._heap)\n\n    def push(self, value: T, priority: P) -> int:\n        \"\"\"Dodaj wartość z priorytetem i zwróć jej unikalny uchwyt.\n\n        Ta sama wartość może zostać dodana wielokrotnie; każde wystąpienie\n        otrzymuje osobny uchwyt.\n        \"\"\"\n        handle = self._next_handle\n        self._next_handle += 1\n\n        index = len(self._heap)\n        self._heap.append(_Entry(value, priority, handle))\n        self._positions[handle] = index\n        self._sift_up(index)\n        return handle\n\n    def peek(self) -> Tuple[P, T]:\n        \"\"\"Zwróć parę (priorytet, wartość) bez usuwania elementu.\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"peek z pustej kolejki priorytetowej\")\n\n        entry = self._heap[0]\n        return entry.priority, entry.value\n\n    def pop(self) -> Tuple[P, T]:\n        \"\"\"Usuń i zwróć parę (priorytet, wartość) o najmniejszym priorytecie.\n\n        Uchwyt usuniętego elementu przestaje być ważny.\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"pop z pustej kolejki priorytetowej\")\n\n        root = self._heap[0]\n        last = self._heap.pop()\n        del self._positions[root.handle]\n\n        if self._heap:\n            # Ostatni element zastępuje korzeń; naprawiamy kopiec w dół.\n            self._heap[0] = last\n            self._positions[last.handle] = 0\n            self._sift_down(0)\n\n        return root.priority, root.value\n\n    def decrease_key(self, handle: int, new_priority: P) -> None:\n        \"\"\"Zmniejsz priorytet elementu wskazanego uchwytem.\n\n        Równy priorytet jest dozwolony. Zmiana priorytetu nie zmienia\n        pierwotnej kolejności dodania używanej do rozstrzygania remisów.\n\n        Raises:\n            KeyError: Jeśli uchwyt nie istnieje lub element został usunięty.\n            ValueError: Jeśli nowy priorytet jest większy od poprzedniego.\n        \"\"\"\n        try:\n            index = self._positions[handle]\n        except KeyError:\n            raise KeyError(f\"Nieznany lub nieaktywny uchwyt: {handle}\") from None\n\n        entry = self._heap[index]\n        if entry.priority < new_priority:\n            raise ValueError(\"Nowy priorytet nie może być większy od obecnego\")\n\n        entry.priority = new_priority\n        # Po zmniejszeniu priorytetu naruszenie może wystąpić tylko w górę.\n        self._sift_up(index)\n\n    def _less(self, left: int, right: int) -> bool:\n        \"\"\"Sprawdź, czy element left powinien poprzedzać element right.\"\"\"\n        a = self._heap[left]\n        b = self._heap[right]\n\n        if a.priority < b.priority:\n            return True\n        if b.priority < a.priority:\n            return False\n\n        # Rosnące uchwyty zapewniają stabilność bez porównywania wartości.\n        return a.handle < b.handle\n\n    def _swap(self, left: int, right: int) -> None:\n        \"\"\"Zamień elementy i zaktualizuj mapę ich położeń.\"\"\"\n        self._heap[left], self._heap[right] = (\n            self._heap[right],\n            self._heap[left],\n        )\n        self._positions[self._heap[left].handle] = left\n        self._positions[self._heap[right].handle] = right\n\n    def _sift_up(self, index: int) -> None:\n        \"\"\"Przesuwaj element w górę, aż zostanie przywrócony porządek kopca.\"\"\"\n        while index > 0:\n            parent = (index - 1) // 2\n            if not self._less(index, parent):\n                break\n            self._swap(index, parent)\n            index = parent\n\n    def _sift_down(self, index: int) -> None:\n        \"\"\"Przesuwaj element w dół, wybierając mniejsze z jego dzieci.\"\"\"\n        size = len(self._heap)\n\n        while True:\n            left = 2 * index + 1\n            if left >= size:\n                return\n\n            right = left + 1\n            smallest = left\n\n            if right < size and self._less(right, left):\n                smallest = right\n\n            if not self._less(smallest, index):\n                return\n\n            self._swap(index, smallest)\n            index = smallest",
   "czas_s": 21.425,
   "usage": {
    "prompt_tokens": 64,
    "completion_tokens": 1574,
    "total_tokens": 1638,
    "cost": 0.015868,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.015868,
     "upstream_inference_prompt_cost": 0.000128,
     "upstream_inference_completions_cost": 0.01574
    },
    "completion_tokens_details": {
     "reasoning_tokens": 136,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.015868,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.015868,
   "ttft_s": 8.172,
   "ttft_dowolny_token_s": 6.638,
   "gen_s": 14.787,
   "tok_s": 106.4,
   "slowa": 489,
   "slowa_na_s_calkowity_czas": 22.8,
   "tokeny": {
    "wej": 64,
    "wyj": 1574,
    "rozumowanie": 136
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S3_tlumaczenie#1",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Przetłumacz na angielski, zachowując akapity:\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "On Wednesday, Brzeziny Górne City Council adopted the “Bike Around the Corner” programme. From 1 March 2027, the city is set to have 14 bike-sharing stations, including four near secondary schools and two by the railway station. A total of 160 bicycles will be available for hire, including 40 electric-assist bikes. The first 20 minutes of each ride will be free, with each subsequent hour or part thereof costing PLN 3, or PLN 5 for an electric bike.\n\nThe programme will cost PLN 2.1 million over three years. Half will be covered by a grant from a regional fund, with the remainder coming from the city budget. The operator will be a company selected through a tender, which is due to be announced by the end of December. Opposition councillors voted against the programme, pointing out that the city still lacks dedicated cycle paths: it currently has 11 km, with plans to reach 26 km by 2028. The mayor says the bike-sharing system and new routes will be developed in parallel, and that the first 6 km of new cycle paths will open before the bike hire service launches.\n\nResidents of the Zatorze neighbourhood, which will not receive a station, also attended the council meeting. Officials promised to analyse rental data after the first season and consider expanding the network. A survey conducted in the summer showed that 38 per cent of adult residents would like to use the bike-sharing service, most often to travel to work or catch a train.",
   "czas_s": 5.45,
   "usage": {
    "prompt_tokens": 451,
    "completion_tokens": 314,
    "total_tokens": 765,
    "cost": 0.004042,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.004042,
     "upstream_inference_prompt_cost": 0.000902,
     "upstream_inference_completions_cost": 0.00314
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.004042,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.004042,
   "ttft_s": 1.278,
   "ttft_dowolny_token_s": 1.278,
   "gen_s": 4.172,
   "tok_s": 75.3,
   "slowa": 251,
   "slowa_na_s_calkowity_czas": 46.1,
   "tokeny": {
    "wej": 451,
    "wyj": 314,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S1_esej#2",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Napisz po polsku tekst na około 500 słów o tym, jak działa kompostownik w ogrodzie. Bez wstępów i podsumowań typu „podsumowując”.",
   "odpowiedz": "Kompostownik w ogrodzie służy do przetwarzania resztek roślinnych w naturalny materiał użyźniający glebę. Działają w nim przede wszystkim bakterie, grzyby oraz drobne organizmy, takie jak roztocza, skoczogonki i dżdżownice. Rozkładają one materię organiczną na prostsze związki. Aby proces przebiegał sprawnie i bez przykrego zapachu, potrzebują odpowiedniej ilości tlenu, wilgoci oraz zróżnicowanego pokarmu.\n\nNajlepiej ustawić kompostownik w miejscu lekko zacienionym, osłoniętym od silnego wiatru, bezpośrednio na ziemi. Kontakt z podłożem umożliwia organizmom glebowym przemieszczanie się do wnętrza pryzmy i ułatwia odpływ nadmiaru wody. Na dnie warto ułożyć warstwę drobnych gałązek, które zapewniają przestrzenie powietrzne. Kompostownik powinien być łatwo dostępny, także z taczką, ponieważ regularne dokładanie odpadów i wybieranie gotowego kompostu wymaga wygodnego dojścia.\n\nDo środka trafiają dwa podstawowe rodzaje materiałów. Pierwsze to materiały „zielone”, zwykle wilgotne i bogate w azot: obierki warzyw i owoców, fusy po kawie, świeża trawa oraz miękkie części roślin. Drugą grupę stanowią materiały „brązowe”, bogate w węgiel: suche liście, słoma, rozdrobnione gałązki i niezadrukowana tektura bez taśmy oraz powłok. Ich mieszanie pomaga zachować właściwą strukturę kompostu. Orientacyjnie można dodawać dwie–trzy objętości materiałów brązowych na jedną objętość zielonych, dostosowując proporcje do wilgotności i tempa rozkładu.\n\nŚwieżo skoszonej trawy nie należy wrzucać grubą, zwartą warstwą. Łatwo się zbija, odcina dopływ powietrza i zaczyna gnić. Lepiej podsuszyć ją albo wymieszać z liśćmi i drobnymi gałązkami. Większe odpady warto rozdrobnić, ponieważ zwiększa to powierzchnię dostępną dla mikroorganizmów. Nie trzeba jednak rozdrabniać wszystkiego bardzo drobno: nieco grubszych fragmentów pomaga utrzymać przewiewność pryzmy.\n\nWilgotność zawartości powinna przypominać dobrze odciśniętą gąbkę. Jeśli materiał jest suchy i niemal się nie zmienia, można delikatnie go podlać. Gdy jest mokry, zbity i wydziela nieprzyjemny zapach, należy dodać suchych składników oraz go przemieszać. Przerzucanie kompostu widłami co kilka tygodni dostarcza tlenu i przenosi słabiej rozłożone fragmenty do bardziej aktywnego wnętrza. Podczas suszy lub długotrwałego deszczu pomocna bywa pokrywa, która ogranicza skrajne zmiany wilgotności, ale nie zamyka szczelnie dopływu powietrza.\n\nW większej, dobrze przygotowanej pryzmie temperatura może wzrosnąć do około 50–65°C. To efekt intensywnej pracy mikroorganizmów, a nie nagrzewania przez słońce. W małym kompostowniku materiał często pozostaje chłodniejszy, lecz nadal się rozkłada, tylko wolniej. Zimą aktywność organizmów spada, dlatego proces może wyraźnie zwolnić. Wiosną zwykle ponownie przyspiesza.\n\nDo zwykłego kompostownika ogrodowego lepiej nie dodawać mięsa, ryb, nabiału, tłuszczów ani resztek gotowanych potraw. Mogą przyciągać gryzonie i powodować uciążliwe zapachy. Nie nadają się również odchody psów i kotów, drewno impregnowane, tworzywa sztuczne oraz popiół z węgla. Chore rośliny i chwasty z dojrzałymi nasionami warto zagospodarować inaczej, ponieważ domowy kompost nie zawsze osiąga temperaturę wystarczającą do ich unieszkodliwienia.\n\nGotowy kompost powstaje zwykle po kilku do kilkunastu miesiącach. Jest ciemny, sypki i pachnie leśną ziemią, a większości pierwotnych składników nie da się już rozpoznać. Można przesiać go przez siatkę, a większe, nierozłożone kawałki ponownie wrzucić do kompostownika. Dojrzały materiał rozkłada się cienką warstwą na grządkach, miesza z wierzchnią warstwą gleby lub wykorzystuje do ściółkowania. Poprawia strukturę podłoża, zwiększa jego zdolność zatrzymywania wody i stopniowo dostarcza roślinom składników pokarmowych.",
   "czas_s": 25.794,
   "usage": {
    "prompt_tokens": 49,
    "completion_tokens": 1176,
    "total_tokens": 1225,
    "cost": 0.011858,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.011858,
     "upstream_inference_prompt_cost": 9.8e-05,
     "upstream_inference_completions_cost": 0.01176
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.011858,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.011858,
   "ttft_s": 1.447,
   "ttft_dowolny_token_s": 1.447,
   "gen_s": 24.347,
   "tok_s": 48.3,
   "slowa": 486,
   "slowa_na_s_calkowity_czas": 18.8,
   "tokeny": {
    "wej": 49,
    "wyj": 1176,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S2_kod#2",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Napisz w Pythonie kompletną implementację kolejki priorytetowej opartej na kopcu binarnym (klasa z metodami push, pop, peek, __len__, decrease_key) z docstringami i komentarzami. Tylko kod.",
   "odpowiedz": "from itertools import count\nfrom typing import Dict, Generic, Hashable, List, Tuple, TypeVar\n\nT = TypeVar(\"T\", bound=Hashable)\n\n\nclass PriorityQueue(Generic[T]):\n    \"\"\"Stabilna kolejka priorytetowa oparta na binarnym kopcu minimalnym.\n\n    Mniejsza wartość priorytetu oznacza wcześniejsze pobranie elementu.\n    Elementy o równych priorytetach są pobierane w kolejności dodania.\n    Każdy element musi być hashowalny i unikatowy w kolejce. Jego hash\n    i relacja równości nie mogą zmieniać się podczas pobytu w kolejce.\n\n    Priorytety muszą być wzajemnie porównywalnymi liczbami, innymi niż NaN.\n\n    Złożoność:\n        push, pop, decrease_key: O(log n).\n        peek, __len__: O(1).\n        Pamięć: O(n).\n    \"\"\"\n\n    def __init__(self) -> None:\n        \"\"\"Tworzy pustą kolejkę.\"\"\"\n        # Licznik rozstrzyga remisy bez porównywania samych elementów.\n        self._heap: List[Tuple[float, int, T]] = []\n        self._positions: Dict[T, int] = {}\n        self._counter = count()\n\n    def __len__(self) -> int:\n        \"\"\"Zwraca liczbę elementów w kolejce.\"\"\"\n        return len(self._heap)\n\n    def push(self, item: T, priority: float) -> None:\n        \"\"\"Dodaje element z podanym priorytetem.\n\n        Raises:\n            ValueError: Jeśli element już istnieje lub priorytet to NaN.\n            TypeError: Jeśli element nie jest hashowalny.\n        \"\"\"\n        self._validate_priority(priority)\n        if item in self._positions:\n            raise ValueError(\"Element już znajduje się w kolejce.\")\n\n        index = len(self._heap)\n        self._heap.append((priority, next(self._counter), item))\n        self._positions[item] = index\n        self._sift_up(index)\n\n    def pop(self) -> T:\n        \"\"\"Usuwa i zwraca element o najmniejszym priorytecie.\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"Nie można pobrać elementu z pustej kolejki.\")\n\n        item = self._heap[0][2]\n        last = self._heap.pop()\n        del self._positions[item]\n\n        if self._heap:\n            self._heap[0] = last\n            self._positions[last[2]] = 0\n            self._sift_down(0)\n\n        return item\n\n    def peek(self) -> T:\n        \"\"\"Zwraca element o najmniejszym priorytecie bez usuwania go.\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"Nie można podejrzeć pustej kolejki.\")\n        return self._heap[0][2]\n\n    def decrease_key(self, item: T, new_priority: float) -> None:\n        \"\"\"Zmniejsza priorytet elementu, zachowując jego kolejność dodania.\n\n        Ustawienie priorytetu równego dotychczasowemu niczego nie zmienia.\n\n        Raises:\n            KeyError: Jeśli element nie znajduje się w kolejce.\n            ValueError: Jeśli nowy priorytet jest większy lub jest NaN.\n        \"\"\"\n        index = self._positions[item]\n        self._validate_priority(new_priority)\n        old_priority, order, stored_item = self._heap[index]\n\n        if new_priority > old_priority:\n            raise ValueError(\"Nowy priorytet nie może być większy od starego.\")\n        if new_priority == old_priority:\n            return\n\n        self._heap[index] = (new_priority, order, stored_item)\n        # Po zmniejszeniu priorytetu naruszenie może wystąpić tylko w górę.\n        self._sift_up(index)\n\n    @staticmethod\n    def _validate_priority(priority: float) -> None:\n        \"\"\"Odrzuca NaN, które nie zapewnia poprawnego porządku kopca.\"\"\"\n        if priority != priority:\n            raise ValueError(\"Priorytet nie może być NaN.\")\n\n    def _swap(self, first: int, second: int) -> None:\n        \"\"\"Zamienia węzły i aktualizuje mapę ich pozycji.\"\"\"\n        self._heap[first], self._heap[second] = (\n            self._heap[second],\n            self._heap[first],\n        )\n        self._positions[self._heap[first][2]] = first\n        self._positions[self._heap[second][2]] = second\n\n    def _sift_up(self, index: int) -> None:\n        \"\"\"Przesuwa węzeł w górę do przywrócenia własności kopca.\"\"\"\n        while index > 0:\n            parent = (index - 1) // 2\n            if not self._heap[index] < self._heap[parent]:\n                break\n            self._swap(index, parent)\n            index = parent\n\n    def _sift_down(self, index: int) -> None:\n        \"\"\"Przesuwa węzeł w dół do przywrócenia własności kopca.\"\"\"\n        size = len(self._heap)\n\n        while True:\n            left = 2 * index + 1\n            right = left + 1\n            smallest = index\n\n            if left < size and self._heap[left] < self._heap[smallest]:\n                smallest = left\n            if right < size and self._heap[right] < self._heap[smallest]:\n                smallest = right\n\n            if smallest == index:\n                break\n\n            self._swap(index, smallest)\n            index = smallest",
   "czas_s": 19.306,
   "usage": {
    "prompt_tokens": 64,
    "completion_tokens": 1432,
    "total_tokens": 1496,
    "cost": 0.014448,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.014448,
     "upstream_inference_prompt_cost": 0.000128,
     "upstream_inference_completions_cost": 0.01432
    },
    "completion_tokens_details": {
     "reasoning_tokens": 172,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.014448,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.014448,
   "ttft_s": 8.804,
   "ttft_dowolny_token_s": 8.804,
   "gen_s": 10.502,
   "tok_s": 136.4,
   "slowa": 433,
   "slowa_na_s_calkowity_czas": 22.4,
   "tokeny": {
    "wej": 64,
    "wyj": 1432,
    "rozumowanie": 172
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S3_tlumaczenie#2",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Przetłumacz na angielski, zachowując akapity:\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "Brzeziny Górne City Council adopted the “Bike Around the Corner” programme on Wednesday. From 1 March 2027, the city is set to have 14 bike-sharing stations, including four at secondary schools and two at the railway station. A total of 160 bicycles will be available for hire, including 40 electric-assist bikes. The first 20 minutes of each ride will be free, with each subsequent hour or part thereof costing 3 złoty, or 5 złoty for an electric bike.\n\nThe programme will cost 2.1 million złoty over three years. Half will be covered by a grant from a regional fund, with the remainder coming from the city budget. The operator will be a company selected through a tender, which is due to be announced by the end of December. Opposition councillors voted against the programme, pointing out that the city still lacks dedicated cycle paths: it currently has 11 km, with plans to increase this to 26 km by 2028. The mayor says the bike-sharing scheme and new routes will be developed in parallel, and that the first 6 km of new cycle paths will open before the bike-sharing service launches.\n\nResidents of the Zatorze neighbourhood, which will not receive any stations, also attended the council meeting. Officials promised to analyse rental data after the first season and consider expanding the network. A survey conducted in the summer found that 38 per cent of adult residents would like to use the bike-sharing service, most often to commute to work or catch a train.",
   "czas_s": 5.453,
   "usage": {
    "prompt_tokens": 451,
    "completion_tokens": 318,
    "total_tokens": 769,
    "cost": 0.004082,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.004082,
     "upstream_inference_prompt_cost": 0.000902,
     "upstream_inference_completions_cost": 0.00318
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.004082,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.004082,
   "ttft_s": 1.523,
   "ttft_dowolny_token_s": 1.523,
   "gen_s": 3.93,
   "tok_s": 80.9,
   "slowa": 252,
   "slowa_na_s_calkowity_czas": 46.2,
   "tokeny": {
    "wej": 451,
    "wyj": 318,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S1_esej#1",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Napisz po polsku tekst na około 500 słów o tym, jak działa kompostownik w ogrodzie. Bez wstępów i podsumowań typu „podsumowując”.",
   "odpowiedz": "Kompostownik działa na zasadzie kontrolowanego rozkładu materii organicznej przez mikroorganizmy, grzyby oraz drobne zwierzęta glebowe, takie jak dżdżownice, nicienie czy stonogi. Proces ten wymaga ścisłej współpracy między tymi organizmami a odpowiednimi warunkami środowiskowymi, na które składają się dostęp do tlenu, właściwa wilgotność oraz zbilansowany skład chemiczny resztek. Podstawą funkcjonowania tego ogrodowego bioreaktora jest naprzemienne układanie warstw materiałów bogatych w węgiel (tzw. brązowych) oraz materiałów obfitujących w azot (tzw. zielonych). Węgiel dostarcza mikroorganizmom energii niezbędnej do życia, natomiast azot stanowi budulec dla ich białek i pozwala na szybkie rozmnażanie się populacji bakterii. \n\nDo frakcji węglowej zaliczają się suche liście, rozdrobnione gałęzie, słoma, kora, trociny oraz niezadrukowana tektura. Frakcję azotową tworzą skoszona trawa, resztki warzyw i owoców z kuchni, fusy po kawie i herbacie oraz zwiędłe rośliny ogrodowe. Zachowanie proporcji, w której na jedną część składników zielonych przypadają około dwie lub trzy części składników brązowych, gwarantuje prawidłowy przebieg procesu. Zbyt duża ilość azotu prowadzi do gnicia i powstawania nieprzyjemnego zapachu amoniaku, z kolei nadmiar węgla drastycznie spowalnia tempo rozkładu, ponieważ bakteriom brakuje budulca do namnażania.\n\nRozkład materii w kompostowniku dzieli się na kilka wyraźnych faz termicznych i biologicznych. Pierwsza to faza mezofilna, trwająca zazwyczaj kilka dni. Bakterie obecne na resztkach roślinnych zaczynają intensywnie rozkładać najprostsze związki, takie jak cukry i aminokwasy. W wyniku ich metabolizmu wydziela się ciepło, co podnosi temperaturę wewnątrz pryzmy do około 40 stopni Celsjusza. W tym momencie następuje faza termofilna. Pałeczkę przejmują bakterie ciepłolubne oraz promieniowce, które rozkładają bardziej złożone substancje, w tym celulozę i hemicelulozę. Temperatura w sercu kompostownika może wtedy wzrosnąć nawet do 60–70 stopni Celsjusza. Tak wysoka ciepłota ma kluczowe znaczenie higieniczne, ponieważ niszczy patogeny chorobotwórcze oraz nasiona chwastów, zapobiegając ich późniejszemu rozsiewaniu w ogrodzie.\n\nGdy zapasy łatwo dostępnego pożywienia wyczerpują się, aktywność bakterii termofilnych spada, a pryzma zaczyna stygnąć. Rozpoczyna się faza chłodzenia i dojrzewania, która trwa od kilku miesięcy do ponad roku. Do wnętrza kompostownika wkraczają grzyby, które potrafią rozłożyć najtwardsze struktury roślinne, takie jak lignina zawarta w zdrewniałych łodygach. Równocześnie pryzmę kolonizują bezkręgowce. Dżdżownice pełnią tu rolę inżynierów: drążą korytarze, napowietrzają masę, mieszają warstwy i trawią rozkładającą się materię, wydalając ją w postaci niezwykle żyznego biohumusu. W tym etapie powstają kwasy humusowe, które nadają kompostowi charakterystyczny, ciemnobrązowy kolor i zapach świeżej ziemi leśnej.\n\nAby te procesy zachodziły bez zakłóceń, masa kompostowa musi mieć wilgotność przypominającą wyciśniętą gąbkę. Woda jest niezbędna bakteriom do poruszania się i pobierania składników odżywczych. Jeśli pryzma przeschnie, procesy biologiczne zamierają; jeśli jest zbyt mokra, woda wypiera powietrze z przestrzeni między cząsteczkami. Brak tlenu zabija pożyteczne bakterie tlenowe, a ich miejsce zajmują bakterie beztlenowe, które prowadzą proces gnicia, produkując metan i siarkowodór. Dlatego regularne przerzucanie zawartości kompostownika widłami jest tak istotne – zabieg ten wtłacza świeże powietrze do środka pryzmy i pozwala na równomierny rozkład materiału z brzegów, który trafia do cieplejszego centrum. Do kompostownika nie należy wrzucać mięsa, kości, nabiału, tłuszczów ani odchodów zwierząt mięsożernych, ponieważ zakłócają one równowagę mikrobiologiczną, przyciągają gryzonie i wprowadzają groźne pasożyty, których temperatura w przydomowym kompostowniku może nie zneutralizować.",
   "czas_s": 20.203,
   "usage": {
    "prompt_tokens": 73,
    "completion_tokens": 1110,
    "total_tokens": 1183,
    "cost": 0.011246,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.011246,
     "upstream_inference_prompt_cost": 0.000146,
     "upstream_inference_completions_cost": 0.0111
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.011246,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.011246,
   "ttft_s": 3.549,
   "ttft_dowolny_token_s": 3.549,
   "gen_s": 16.654,
   "tok_s": 66.7,
   "slowa": 495,
   "slowa_na_s_calkowity_czas": 24.5,
   "tokeny": {
    "wej": 73,
    "wyj": 1110,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S2_kod#1",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Napisz w Pythonie kompletną implementację kolejki priorytetowej opartej na kopcu binarnym (klasa z metodami push, pop, peek, __len__, decrease_key) z docstringami i komentarzami. Tylko kod.",
   "odpowiedz": "```python\nfrom typing import Any, List, Tuple, Dict\n\nclass PriorityQueue:\n    \"\"\"\n    Kolejka priorytetowa oparta na kopcu binarnym (Min-Heap).\n    \n    Przechowuje elementy wraz z ich priorytetami. Element o najniższej wartości\n    priorytetu znajduje się zawsze na szczycie kopca. Obsługuje operację \n    decrease_key w czasie O(log n) dzięki wewnętrznemu słownikowi mapującemu \n    elementy na ich indeksy w kopcu.\n    \"\"\"\n\n    def __init__(self) -> None:\n        \"\"\"Inicjalizuje pustą kolejkę priorytetową.\"\"\"\n        # Lista przechowująca krotki (priorytet, element) reprezentująca kopiec\n        self._heap: List[Tuple[Any, Any]] = []\n        # Słownik mapujący element na jego aktualny indeks w tablicy _heap.\n        # Wymagany do szybkiego (O(1)) wyszukiwania elementu w metodzie decrease_key.\n        # Uwaga: Elementy muszą być haszowalne (hashable) i unikalne.\n        self._position_map: Dict[Any, int] = {}\n\n    def __len__(self) -> int:\n        \"\"\"Zwraca liczbę elementów w kolejce.\"\"\"\n        return len(self._heap)\n\n    def push(self, item: Any, priority: Any) -> None:\n        \"\"\"\n        Dodaje nowy element do kolejki z określonym priorytetem.\n\n        :param item: Element do dodania (musi być haszowalny i unikalny).\n        :param priority: Priorytet elementu (mniejsza wartość = wyższy priorytet).\n        :raises ValueError: Jeśli element już znajduje się w kolejce.\n        \"\"\"\n        if item in self._position_map:\n            raise ValueError(f\"Element '{item}' już istnieje w kolejce.\")\n\n        # Dodaj nowy element na koniec kopca\n        index = len(self._heap)\n        self._heap.append((priority, item))\n        self._position_map[item] = index\n\n        # Przywróć własność kopca idąc w górę\n        self._sift_up(index)\n\n    def pop(self) -> Tuple[Any, Any]:\n        \"\"\"\n        Usuwa i zwraca element o najwyższym priorytecie (najmniejszej wartości).\n\n        :return: Krotka (element, priorytet).\n        :raises IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"Próba pobrania elementu z pustej kolejki (pop).\")\n\n        # Zamień korzeń (najmniejszy element) z ostatnim elementem w kopcu\n        last_index = len(self._heap) - 1\n        self._swap(0, last_index)\n\n        # Usuń ostatni element (dawny korzeń) i zaktualizuj mapę pozycji\n        priority, item = self._heap.pop()\n        del self._position_map[item]\n\n        # Jeśli w kopcu zostały jeszcze elementy, przywróć własność kopca idąc w dół\n        if self._heap:\n            self._sift_down(0)\n\n        return item, priority\n\n    def peek(self) -> Tuple[Any, Any]:\n        \"\"\"\n        Zwraca element o najwyższym priorytecie bez usuwania go z kolejki.\n\n        :return: Krotka (element, priorytet).\n        :raises IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"Próba podglądu pustej kolejki (peek).\")\n        \n        priority, item = self._heap[0]\n        return item, priority\n\n    def decrease_key(self, item: Any, new_priority: Any) -> None:\n        \"\"\"\n        Zmniejsza wartość priorytetu dla istniejącego elementu (zwiększa jego ważność)\n        i aktualizuje jego pozycję w kopcu.\n\n        :param item: Element, którego priorytet ma zostać zmieniony.\n        :param new_priority: Nowa, mniejsza wartość priorytetu.\n        :raises KeyError: Jeśli element nie znajduje się w kolejce.\n        :raises ValueError: Jeśli nowy priorytet jest większy niż obecny.\n        \"\"\"\n        if item not in self._position_map:\n            raise KeyError(f\"Element '{item}' nie znajduje się w kolejce.\")\n\n        index = self._position_map[item]\n        current_priority, _ = self._heap[index]\n\n        if new_priority > current_priority:\n            raise ValueError(\"Nowy priorytet musi być mniejszy lub równy obecnemu.\")\n\n        # Zaktualizuj priorytet elementu\n        self._heap[index] = (new_priority, item)\n\n        # Ponieważ priorytet zmalał, element może naruszać własność kopca\n        # tylko względem swojego rodzica, więc przesuwamy go w górę.\n        self._sift_up(index)\n\n    def _sift_up(self, index: int) -> None:\n        \"\"\"\n        Przesuwa element na podanym indeksie w górę kopca, dopóki nie zostanie\n        przywrócona własność kopca (rodzic <= dziecko).\n        \"\"\"\n        parent_index = (index - 1) // 2\n\n        # Dopóki nie jesteśmy w korzeniu i obecny element ma mniejszy klucz niż rodzic\n        while index > 0 and self._heap[index][0] < self._heap[parent_index][0]:\n            self._swap(index, parent_index)\n            index = parent_index\n            parent_index = (index - 1) // 2\n\n    def _sift_down(self, index: int) -> None:\n        \"\"\"\n        Przesuwa element na podanym indeksie w dół kopca, dopóki nie zostanie\n        przywrócona własność kopca (rodzic <= dzieci).\n        \"\"\"\n        size = len(self._heap)\n\n        while True:\n            smallest = index\n            left_child = 2 * index + 1\n            right_child = 2 * index + 2\n\n            # Sprawdź, czy lewe dziecko istnieje i jest mniejsze od obecnego najmniejszego\n            if left_child < size and self._heap[left_child][0] < self._heap[smallest][0]:\n                smallest = left_child\n\n            # Sprawdź, czy prawe dziecko istnieje i jest mniejsze od obecnego najmniejszego\n            if right_child < size and self._heap[right_child][0] < self._heap[smallest][0]:\n                smallest = right_child\n\n            # Jeśli najmniejszym elementem jest nadal rodzic, kopiec jest poprawny\n            if smallest == index:\n                break\n\n            # W przeciwnym razie zamień rodzica z mniejszym dzieckiem i kontynuuj w dół\n            self._swap(index, smallest)\n            index = smallest\n\n    def _swap(self, i: int, j: int) -> None:\n        \"\"\"\n        Zamienia miejscami dwa elementy w kopcu o indeksach i oraz j,\n        aktualizując przy tym ich pozycje w słowniku _position_map.\n        \"\"\"\n        # Pobierz elementy z krotek (priorytet, element)\n        item_i = self._heap[i][1]\n        item_j = self._heap[j][1]\n\n        # Zamień krotki w tablicy kopca\n        self._heap[i], self._heap[j] = self._heap[j], self._heap[i]\n\n        # Zaktualizuj indeksy w mapie pozycji\n        self._position_map[item_i] = j\n        self._position_map[item_j] = i\n\n\nif __name__ == \"__main__\":\n    # Przykładowe użycie i testy\n    pq = PriorityQueue()\n    \n    # Dodawanie elementów\n    pq.push(\"Zadanie A\", 5)\n    pq.push(\"Zadanie B\", 2)\n    pq.push(\"Zadanie C\", 8)\n    pq.push(\"Zadanie D\", 1)\n\n    assert len(pq) == 4\n    assert pq.peek() == (\"Zadanie D\", 1)\n\n    # Zmiana priorytetu (decrease_key)\n    pq.decrease_key(\"Zadanie C\", 0)\n    assert pq.peek() == (\"Zadanie C\", 0)\n\n    # Pobieranie elementów w kolejności priorytetów\n    results = []\n    while len(pq) > 0:\n        results.append(pq.pop())\n\n    expected = [\n        (\"Zadanie C\", 0),\n        (\"Zadanie D\", 1),\n        (\"Zadanie B\", 2),\n        (\"Zadanie A\", 5)\n    ]\n    assert results == expected\n```",
   "czas_s": 29.006,
   "usage": {
    "prompt_tokens": 88,
    "completion_tokens": 2058,
    "total_tokens": 2146,
    "cost": 0.020756,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.020756,
     "upstream_inference_prompt_cost": 0.000176,
     "upstream_inference_completions_cost": 0.02058
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.020756,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.020756,
   "ttft_s": 2.516,
   "ttft_dowolny_token_s": 2.516,
   "gen_s": 26.49,
   "tok_s": 77.7,
   "slowa": 683,
   "slowa_na_s_calkowity_czas": 23.5,
   "tokeny": {
    "wej": 88,
    "wyj": 2058,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S3_tlumaczenie#1",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Przetłumacz na angielski, zachowując akapity:\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "The Brzeziny Górne City Council adopted the \"Bike Around the Corner\" program on Wednesday. Starting March 1, 2027, 14 city bike stations are set to operate in the town, including four near high schools and two at the railway station. In total, 160 bicycles will be available for rent, 40 of which will be electric-assisted. The first 20 minutes of each ride will be free, and each subsequent started hour will cost 3 PLN, or 5 PLN for an electric bike.\n\nThe program will cost 2.1 million PLN over three years. Half will be covered by a grant from the regional fund, and the rest by the city budget. The operator will be a company selected through a tender, which is to be announced by the end of December. Opposition councilors voted against it, pointing out that the city still lacks dedicated cycle paths: currently, there are 11 km of them, and according to the plan, there will be 26 km by 2028. The mayor responds that the city bikes and new routes will be developed in parallel, and the first 6 km of new paths will be completed even before the rental system launches.\n\nResidents of the Zatorze district, which will not get a single station, also attended the session. Officials promised that after the first season they would analyze the rental data and consider expanding the network. A survey conducted in the summer showed that 38 percent of adult residents would like to use the city bike system, most often on their way to work or to catch a train.",
   "czas_s": 8.446,
   "usage": {
    "prompt_tokens": 424,
    "completion_tokens": 338,
    "total_tokens": 762,
    "cost": 0.004228,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.004228,
     "upstream_inference_prompt_cost": 0.000848,
     "upstream_inference_completions_cost": 0.00338
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.004228,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.004228,
   "ttft_s": 3.965,
   "ttft_dowolny_token_s": 3.965,
   "gen_s": 4.482,
   "tok_s": 75.4,
   "slowa": 262,
   "slowa_na_s_calkowity_czas": 31.0,
   "tokeny": {
    "wej": 424,
    "wyj": 338,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S1_esej#2",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Napisz po polsku tekst na około 500 słów o tym, jak działa kompostownik w ogrodzie. Bez wstępów i podsumowań typu „podsumowując”.",
   "odpowiedz": "Kompostownik w ogrodzie funkcjonuje jak miniaturowa fabryka biochemiczna, w której odpady organiczne ulegają przekształceniu w bogaty w składniki odżywcze humus. Podstawą tego zjawiska jest kontrolowany rozkład tlenowy, za który odpowiada armia miliardów mikroorganizmów, takich jak bakterie tlenowe, promieniowce i grzyby, wspierana przez makroorganizmy glebowe, w tym dżdżownice, stonogi czy drobne chrząszcze. Zwierzęta te fizycznie rozdrabniają materię, a mikroorganizmy rozkładają ją na poziomie chemicznym. Aby ten ekosystem działał sprawnie, wymaga odpowiedniego zbilansowania składników, wilgotności oraz dostępu powietrza.\n\nFundamentem prawidłowego kompostowania jest utrzymanie właściwej proporcji między węglem (C) a azotem (N), która w idealnych warunkach wynosi około 30:1. W praktyce ogrodniczej oznacza to mieszanie dwóch rodzajów odpadów: „zielonych” i „brązowych”. Składniki zielone, bogate w azot, to między innymi świeżo skoszona trawa, obierki warzyw i owoców, fusy z kawy oraz zielone chwasty bez nasion. Dostarczają one białka niezbędnego do namnażania się mikroorganizmów. Składniki brązowe stanowią źródło węgla, który daje bakteriom energię. Należą do nich suche liście, rozdrobnione gałęzie, słoma, trociny oraz niezadrukowany karton. Zbyt duża ilość azotu prowadzi do gnicia i powstawania nieprzyjemnego zapachu amoniaku, natomiast nadmiar węgla drastycznie spowalnia cały proces.\n\nKolejnym krytycznym czynnikiem jest tlen. Kompostowanie tlenowe różni się od gnicia (procesu beztlenowego) tym, że nie wydziela odoru, a zamiast tego pachnie świeżą ziemią leśną. Aby zapewnić odpowiednią cyrkulację powietrza, materiały w kompostowniku układa się warstwowo, przeplatając frakcje mokre i zbite (jak trawa) z suchymi i zapewniającymi strukturę (jak gałązki). Co kilka tygodni pryzmę należy przerzucić widłami, co napowietrza głębsze partie i zapobiega ich zbijaniu się w beztlenowe bryły. Równie ważna jest wilgotność – masa kompostowa powinna przypominać wyciśniętą gąbkę. Zbyt suchy kompost przestaje „pracować”, bo mikroorganizmy zapadają w stan uśpienia, a zbyt mokry traci dostęp do tlenu, co wywołuje procesy gnilne.\n\nSam proces rozkładu przebiega w kilku wyraźnych fazach termicznych. Pierwsza to faza mezofilna, trwająca kilka dni, podczas której powszechne bakterie zaczynają rozkładać najłatwiej przyswajalne cukry, podnosząc temperaturę wewnątrz pryzmy do około 40°C. Następnie rozpoczyna się faza termofilna. Specjalistyczne bakterie ciepłolubne przejmują kontrolę, rozkładając białka, tłuszcze i złożone węglowodany, takie jak celuloza. Temperatura w sercu kompostownika może wtedy wzrosnąć nawet do 60–70°C. Tak wysoka ciepłota jest niezwykle korzystna, ponieważ prowadzi do higienizacji masy – giną wówczas patogeny chorobotwórcze oraz większość nasion chwastów.\n\nGdy zasoby łatwo dostępnego pokarmu ulegają wyczerpaniu, aktywność bakterii termofilnych spada, a pryzma zaczyna stygnąć, przechodząc w fazę chłodzenia i dojrzewania. Do wnętrza kompostownika wracają organizmy, które uciekły przed wysoką temperaturą. Grzyby oraz promieniowce zaczynają rozkładać najtwardsze elementy, takie jak lignina zawarta w zdrewniałych częściach roślin. To właśnie promieniowce odpowiadają za charakterystyczny, przyjemny zapach ziemi. Na tym etapie kluczową rolę odgrywają dżdżownice, które drążą korytarze, napowietrzają masę i wydalają tzw. koprolity, czyli najcenniejszą frakcję próchnicy. W zależności od warunków atmosferycznych i stopnia rozdrobnienia materiału, po okresie od kilku do kilkunastu miesięcy pierwotne odpady zmieniają się w ciemny, sypki i jednorodny kompost, gotowy do użycia jako naturalny nawóz.",
   "czas_s": 17.398,
   "usage": {
    "prompt_tokens": 73,
    "completion_tokens": 1060,
    "total_tokens": 1133,
    "cost": 0.010746,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.010746,
     "upstream_inference_prompt_cost": 0.000146,
     "upstream_inference_completions_cost": 0.0106
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.010746,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.010746,
   "ttft_s": 3.322,
   "ttft_dowolny_token_s": 3.322,
   "gen_s": 14.076,
   "tok_s": 75.3,
   "slowa": 467,
   "slowa_na_s_calkowity_czas": 26.8,
   "tokeny": {
    "wej": 73,
    "wyj": 1060,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S2_kod#2",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Napisz w Pythonie kompletną implementację kolejki priorytetowej opartej na kopcu binarnym (klasa z metodami push, pop, peek, __len__, decrease_key) z docstringami i komentarzami. Tylko kod.",
   "odpowiedz": "```python\nfrom typing import Any, Tuple\n\n\nclass PriorityQueue:\n    \"\"\"Implementacja kolejki priorytetowej opartej na kopcu binarnym (Min-Heap).\n\n    Przechowuje elementy wraz z ich priorytetami. Element o najniższej wartości\n    priorytetu znajduje się zawsze na szczycie kopca. Wykorzystuje dodatkowy\n    słownik\n    do mapowania elementów na ich indeksy w kopcu, co pozwala na wykonanie\n    operacji\n    `decrease_key` w czasie logarytmicznym O(log n).\n\n    Uwaga: Przechowywane elementy (`item`) muszą być haszowalne (hashable) i\n    unikalne.\n    \"\"\"\n\n    def __init__(self) -> None:\n        \"\"\"Inicjalizuje pustą kolejkę priorytetową.\"\"\"\n        # Lista przechowująca krotki (priorytet, element) reprezentująca kopiec\n        self._heap: list[Tuple[Any, Any]] = []\n        # Słownik mapujący element na jego aktualny indeks w liście _heap\n        self._position_map: dict[Any, int] = {}\n\n    def __len__(self) -> int:\n        \"\"\"Zwraca liczbę elementów w kolejce.\n\n        Returns:\n            int: Liczba elementów w kopcu.\n        \"\"\"\n        return len(self._heap)\n\n    def push(self, item: Any, priority: Any) -> None:\n        \"\"\"Dodaje nowy element do kolejki z określonym priorytetem.\n\n        Złożoność czasowa: O(log n).\n\n        Args:\n            item (Any): Element do dodania (musi być haszowalny).\n            priority (Any): Priorytet elementu (musi wspierać operacje\n              porównania).\n\n        Raises:\n            ValueError: Jeśli element znajduje się już w kolejce.\n        \"\"\"\n        if item in self._position_map:\n            raise ValueError(\n                f\"Element '{item}' już istnieje w kolejce. Użyj decrease_key.\"\n            )\n\n        # Dodanie elementu na koniec kopca\n        index = len(self._heap)\n        self._heap.append((priority, item))\n        self._position_map[item] = index\n\n        # Przywrócenie własności kopca poprzez przesiewanie w górę\n        self._sift_up(index)\n\n    def pop(self) -> Tuple[Any, Any]:\n        \"\"\"Usuwa i zwraca element o najwyższym priorytecie (najmniejszej wartości).\n\n        Złożoność czasowa: O(log n).\n\n        Returns:\n            Tuple[Any, Any]: Krotka zawierająca (element, priorytet).\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"Próba pobrania elementu z pustej kolejki.\")\n\n        # Zamiana korzenia (najmniejszy element) z ostatnim elementem kopca\n        last_index = len(self._heap) - 1\n        self._swap(0, last_index)\n\n        # Usunięcie ostatniego elementu (dawnego korzenia)\n        priority, item = self._heap.pop()\n        del self._position_map[item]\n\n        # Przywrócenie własności kopca dla nowego korzenia, jeśli kopiec nie jest pusty\n        if self._heap:\n            self._sift_down(0)\n\n        return item, priority\n\n    def peek(self) -> Tuple[Any, Any]:\n        \"\"\"Zwraca element o najwyższym priorytecie bez usuwania go z kolejki.\n\n        Złożoność czasowa: O(1).\n\n        Returns:\n            Tuple[Any, Any]: Krotka zawierająca (element, priorytet).\n\n        Raises:\n            IndexError: Jeśli kolejka jest pusta.\n        \"\"\"\n        if not self._heap:\n            raise IndexError(\"Kolejka jest pusta.\")\n\n        priority, item = self._heap[0]\n        return item, priority\n\n    def decrease_key(self, item: Any, new_priority: Any) -> None:\n        \"\"\"Zmniejsza wartość priorytetu (zwiększa ważność) istniejącego elementu.\n\n        Złożoność czasowa: O(log n) dzięki mapowaniu pozycji.\n\n        Args:\n            item (Any): Element, którego priorytet ma zostać zmieniony.\n            new_priority (Any): Nowa, mniejsza wartość priorytetu.\n\n        Raises:\n            KeyError: Jeśli element nie znajduje się w kolejce.\n            ValueError: Jeśli nowy priorytet jest większy niż obecny.\n        \"\"\"\n        if item not in self._position_map:\n            raise KeyError(f\"Element '{item}' nie znajduje się w kolejce.\")\n\n        index = self._position_map[item]\n        current_priority, _ = self._heap[index]\n\n        if new_priority > current_priority:\n            raise ValueError(\n                \"Nowy priorytet musi być mniejszy lub równy obecnemu.\"\n            )\n\n        # Aktualizacja priorytetu i naprawa kopca w górę\n        self._heap[index] = (new_priority, item)\n        self._sift_up(index)\n\n    def _sift_up(self, index: int) -> None:\n        \"\"\"Przesuwa element w górę kopca w celu przywrócenia jego własności (Min-Heap).\n\n        Args:\n            index (int): Indeks elementu do przesunięcia w górę.\n        \"\"\"\n        parent_idx = (index - 1) // 2\n\n        # Dopóki nie jesteśmy w korzeniu i priorytet rodzica jest większy niż obecnego elementu\n        while index > 0 and self._heap[index][0] < self._heap[parent_idx][0]:\n            self._swap(index, parent_idx)\n            index = parent_idx\n            parent_idx = (index - 1) // 2\n\n    def _sift_down(self, index: int) -> None:\n        \"\"\"Przesuwa element w dół kopca w celu przywrócenia jego własności (Min-Heap).\n\n        Args:\n            index (int): Indeks elementu do przesunięcia w dół.\n        \"\"\"\n        size = len(self._heap)\n\n        while True:\n            left_idx = 2 * index + 1\n            right_idx = 2 * index + 2\n            smallest_idx = index\n\n            # Sprawdzenie, czy lewe dziecko istnieje i ma mniejszy priorytet\n            if (\n                left_idx < size\n                and self._heap[left_idx][0] < self._heap[smallest_idx][0]\n            ):\n                smallest_idx = left_idx\n\n            # Sprawdzenie, czy prawe dziecko istnieje i ma jeszcze mniejszy priorytet\n            if (\n                right_idx < size\n                and self._heap[right_idx][0] < self._heap[smallest_idx][0]\n            ):\n                smallest_idx = right_idx\n\n            # Jeśli rodzic jest mniejszy niż oba dzieci, kopiec jest poprawny\n            if smallest_idx == index:\n                break\n\n            # Zamiana z mniejszym dzieckiem i kontynuacja przesiewania w dół\n            self._swap(index, smallest_idx)\n            index = smallest_idx\n\n    def _swap(self, i: int, j: int) -> None:\n        \"\"\"Zamienia miejscami dwa elementy w kopcu oraz aktualizuje ich pozycje w słowniku.\n\n        Args:\n            i (int): Indeks pierwszego elementu.\n            j (int): Indeks drugiego elementu.\n        \"\"\"\n        # Pobranie nazw elementów przed zamianą\n        item_i = self._heap[i][1]\n        item_j = self._heap[j][1]\n\n        # Zamiana w tablicy kopca\n        self._heap[i], self._heap[j] = self._heap[j], self._heap[i]\n\n        # Aktualizacja indeksów w mapie pozycji\n        self._position_map[item_i] = j\n        self._position_map[item_j] = i\n```",
   "czas_s": 28.543,
   "usage": {
    "prompt_tokens": 88,
    "completion_tokens": 1889,
    "total_tokens": 1977,
    "cost": 0.019066,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.019066,
     "upstream_inference_prompt_cost": 0.000176,
     "upstream_inference_completions_cost": 0.01889
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.019066,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.019066,
   "ttft_s": 6.354,
   "ttft_dowolny_token_s": 6.354,
   "gen_s": 22.189,
   "tok_s": 85.1,
   "slowa": 620,
   "slowa_na_s_calkowity_czas": 21.7,
   "tokeny": {
    "wej": 88,
    "wyj": 1889,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "szybkosc",
   "zadanie": "S3_tlumaczenie#2",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Przetłumacz na angielski, zachowując akapity:\n\nRada Miasta Brzeziny Górne przyjęła w środę program „Rower za rogiem”. Od 1 marca 2027 roku w mieście ma działać 14 stacji rowerów miejskich, w tym cztery przy szkołach średnich i dwie przy dworcu kolejowym. Łącznie do wypożyczenia będzie 160 rowerów, z czego 40 ze wspomaganiem elektrycznym. Pierwsze 20 minut każdego przejazdu będzie bezpłatne, a każda kolejna rozpoczęta godzina ma kosztować 3 złote; za rower elektryczny 5 złotych.\n\nProgram kosztuje 2,1 mln zł w ciągu trzech lat. Połowę pokryje dotacja z funduszu wojewódzkiego, resztę budżet miasta. Operatorem będzie firma wyłoniona w przetargu, który ma zostać ogłoszony do końca grudnia. Radni opozycji głosowali przeciw, wskazując, że w mieście wciąż brakuje wydzielonych dróg rowerowych: obecnie jest ich 11 km, a według planu do 2028 roku ma być 26 km. Burmistrz odpowiada, że rowery miejskie i nowe trasy będą powstawać równolegle, a pierwsze 6 km nowych dróg zostanie oddane jeszcze przed startem wypożyczalni.\n\nNa sesji pojawili się też mieszkańcy osiedla Zatorze, które nie dostanie żadnej stacji. Urzędnicy obiecali, że po pierwszym sezonie przeanalizują dane z wypożyczeń i rozważą rozbudowę sieci. Ankieta przeprowadzona latem pokazała, że z roweru miejskiego chciałoby korzystać 38 procent dorosłych mieszkańców, najczęściej w drodze do pracy lub na pociąg.",
   "odpowiedz": "The Brzeziny Górne City Council adopted the \"Bike Around the Corner\" (\"Rower za rogiem\") program on Wednesday. Starting March 1, 2027, 14 city bike stations are set to operate in the town, including four at high schools and two at the train station. A total of 160 bicycles will be available for rent, 40 of which will be electric-assisted. The first 20 minutes of each ride will be free, and each subsequent started hour will cost 3 PLN; for an electric bike, it will be 5 PLN.\n\nThe program costs 2.1 million PLN over three years. Half will be covered by a grant from the provincial fund, and the rest by the city budget. The operator will be a company selected in a tender scheduled to be announced by the end of December. Opposition councilors voted against it, pointing out that the city still lacks dedicated cycle paths: currently, there are 11 km of them, and according to the plan, there should be 26 km by 2028. The mayor responds that the city bikes and new routes will be developed in parallel, and the first 6 km of new paths will be completed even before the rental system launches.\n\nResidents of the Zatorze housing estate, which will not get a single station, also appeared at the session. Officials promised that after the first season, they will analyze the rental data and consider expanding the network. A survey conducted in the summer showed that 38 percent of adult residents would like to use the city bike system, most often on their way to work or to catch a train.",
   "czas_s": 6.96,
   "usage": {
    "prompt_tokens": 424,
    "completion_tokens": 348,
    "total_tokens": 772,
    "cost": 0.004328,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.004328,
     "upstream_inference_prompt_cost": 0.000848,
     "upstream_inference_completions_cost": 0.00348
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.004328,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.004328,
   "ttft_s": 2.917,
   "ttft_dowolny_token_s": 2.917,
   "gen_s": 4.044,
   "tok_s": 86.1,
   "slowa": 268,
   "slowa_na_s_calkowity_czas": 38.5,
   "tokeny": {
    "wej": 424,
    "wyj": 348,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "H1_kalkulator",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Napisz funkcję Pythona `evaluate(expr: str) -> fractions.Fraction`, która dokładnie oblicza wyrażenie arytmetyczne.\n\nSpecyfikacja:\n- Liczby: całkowite (np. 42) albo dziesiętne z kropką i cyframi po obu stronach (np. 3.25). Zapisy \".5\", \"5.\", \"1.2.3\", \"1e3\" są błędne.\n- Operatory: + - * / ^ oraz nawiasy ( ). Spacje mogą stać między dowolnymi tokenami.\n- Priorytety od najwyższego: (1) potęgowanie ^, łączne prawostronnie; (2) jednoargumentowy minus/plus (prefiks, może się powtarzać, np. \"--3\"); (3) * i /, łączne lewostronnie; (4) + i -, łączne lewostronnie.\n  Zatem -2^2 = -4, 2^-1 = 1/2 (wykładnik może zaczynać się od unarnego minusa), 2^3^2 = 512.\n- Wykładnik musi mieć wartość całkowitą (po obliczeniu), inaczej ValueError. 0 do potęgi ujemnej oraz dzielenie przez zero: ZeroDivisionError.\n- Każde wyrażenie niepoprawne składniowo (puste, niedomknięte nawiasy, \"()\", dwie liczby obok siebie, operator na końcu, \"2**3\", nieznany znak): ValueError.\n- Wynik zawsze jako Fraction (dokładnie, bez floatów).\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nimport re\nfrom fractions import Fraction\n\n_TOKEN_RE = re.compile(r\"\\s*(?:([0-9]+(?:\\.[0-9]+)?)|([-+*/^()]))\")\n\n\ndef _tokenize(expr):\n    tokens = []\n    pos = 0\n    n = len(expr)\n    while pos < n:\n        m = _TOKEN_RE.match(expr, pos)\n        if not m:\n            if expr[pos:].strip() == \"\":\n                break\n            raise ValueError(\"Nieprawidłowy znak na pozycji %d\" % pos)\n        if m.group(1) is not None:\n            tokens.append((\"num\", m.group(1)))\n        else:\n            tokens.append((\"op\", m.group(2)))\n        pos = m.end()\n    return tokens\n\n\ndef _run(tokens, compute):\n    # compute=False: sama kontrola składni (wartości to None)\n    pos = 0\n    n = len(tokens)\n\n    def peek_op():\n        if pos < n and tokens[pos][0] == \"op\":\n            return tokens[pos][1]\n        return None\n\n    def parse_expr():\n        nonlocal pos\n        value = parse_term()\n        while True:\n            op = peek_op()\n            if op not in (\"+\", \"-\"):\n                return value\n            pos += 1\n            right = parse_term()\n            if compute:\n                value = value + right if op == \"+\" else value - right\n\n    def parse_term():\n        nonlocal pos\n        value = parse_unary()\n        while True:\n            op = peek_op()\n            if op not in (\"*\", \"/\"):\n                return value\n            pos += 1\n            right = parse_unary()\n            if compute:\n                if op == \"*\":\n                    value = value * right\n                else:\n                    if right == 0:\n                        raise ZeroDivisionError(\"Dzielenie przez zero\")\n                    value = value / right\n\n    def parse_unary():\n        nonlocal pos\n        negate = False\n        while True:\n            op = peek_op()\n            if op == \"-\":\n                negate = not negate\n                pos += 1\n            elif op == \"+\":\n                pos += 1\n            else:\n                break\n        value = parse_power()\n        if compute and negate:\n            value = -value\n        return value\n\n    def parse_power():\n        nonlocal pos\n        base = parse_atom()\n        if peek_op() == \"^\":\n            pos += 1\n            exponent = parse_unary()  # prawostronna łączność, dozwolony unarny znak\n            if compute:\n                if exponent.denominator != 1:\n                    raise ValueError(\"Wykładnik musi być liczbą całkowitą\")\n                e = exponent.numerator\n                if base == 0 and e < 0:\n                    raise ZeroDivisionError(\"Zero do potęgi ujemnej\")\n                return Fraction(base ** e)\n            return None\n        return base\n\n    def parse_atom():\n        nonlocal pos\n        if pos >= n:\n            raise ValueError(\"Nieoczekiwany koniec wyrażenia\")\n        kind, text = tokens[pos]\n        if kind == \"num\":\n            pos += 1\n            return Fraction(text) if compute else None\n        if text == \"(\":\n            pos += 1\n            value = parse_expr()\n            if peek_op() != \")\":\n                raise ValueError(\"Brak nawiasu zamykającego\")\n            pos += 1\n            return value\n        raise ValueError(\"Nieoczekiwany token: %r\" % text)\n\n    result = parse_expr()\n    if pos != n:\n        raise ValueError(\"Nieoczekiwany token: %r\" % (tokens[pos][1],))\n    return result\n\n\ndef evaluate(expr: str) -> Fraction:\n    if not isinstance(expr, str):\n        raise ValueError(\"Wyrażenie musi być napisem\")\n    tokens = _tokenize(expr)\n    if not tokens:\n        raise ValueError(\"Puste wyrażenie\")\n    _run(tokens, False)          # najpierw pełna kontrola składni\n    result = _run(tokens, True)  # potem dokładne obliczenie\n    return Fraction(result)\n```",
   "czas_s": 22.7,
   "usage": {
    "prompt_tokens": 625,
    "completion_tokens": 2323,
    "total_tokens": 2948,
    "cost": 0.09792,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.09792,
     "upstream_inference_prompt_cost": 0.005,
     "upstream_inference_completions_cost": 0.09292
    },
    "completion_tokens_details": {
     "reasoning_tokens": 991,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.09792,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.09792,
   "ocena": {
    "zaliczone": 33,
    "testy": 33,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "import re\nfrom fractions import Fraction\n\n_TOKEN_RE = re.compile(r\"\\s*(?:([0-9]+(?:\\.[0-9]+)?)|([-+*/^()]))\")\n\n\ndef _tokenize(expr):\n    tokens = []\n    pos = 0\n    n = len(expr)\n    while pos < n:\n        m = _TOKEN_RE.match(expr, pos)\n        if not m:\n            if expr[pos:].strip() == \"\":\n                break\n            raise ValueError(\"Nieprawidłowy znak na pozycji %d\" % pos)\n        if m.group(1) is not None:\n            tokens.append((\"num\", m.group(1)))\n        else:\n            tokens.append((\"op\", m.group(2)))\n        pos = m.end()\n    return tokens\n\n\ndef _run(tokens, compute):\n    # compute=False: sama kontrola składni (wartości to None)\n    pos = 0\n    n = len(tokens)\n\n    def peek_op():\n        if pos < n and tokens[pos][0] == \"op\":\n            return tokens[pos][1]\n        return None\n\n    def parse_expr():\n        nonlocal pos\n        value = parse_term()\n        while True:\n            op = peek_op()\n            if op not in (\"+\", \"-\"):\n                return value\n            pos += 1\n            right = parse_term()\n            if compute:\n                value = value + right if op == \"+\" else value - right\n\n    def parse_term():\n        nonlocal pos\n        value = parse_unary()\n        while True:\n            op = peek_op()\n            if op not in (\"*\", \"/\"):\n                return value\n            pos += 1\n            right = parse_unary()\n            if compute:\n                if op == \"*\":\n                    value = value * right\n                else:\n                    if right == 0:\n                        raise ZeroDivisionError(\"Dzielenie przez zero\")\n                    value = value / right\n\n    def parse_unary():\n        nonlocal pos\n        negate = False\n        while True:\n            op = peek_op()\n            if op == \"-\":\n                negate = not negate\n                pos += 1\n            elif op == \"+\":\n                pos += 1\n            else:\n                break\n        value = parse_power()\n        if compute and negate:\n            value = -value\n        return value\n\n    def parse_power():\n        nonlocal pos\n        base = parse_atom()\n        if peek_op() == \"^\":\n            pos += 1\n            exponent = parse_unary()  # prawostronna łączność, dozwolony unarny znak\n            if compute:\n                if exponent.denominator != 1:\n                    raise ValueError(\"Wykładnik musi być liczbą całkowitą\")\n                e = exponent.numerator\n                if base == 0 and e < 0:\n                    raise ZeroDivisionError(\"Zero do potęgi ujemnej\")\n                return Fraction(base ** e)\n            return None\n        return base\n\n    def parse_atom():\n        nonlocal pos\n        if pos >= n:\n            raise ValueError(\"Nieoczekiwany koniec wyrażenia\")\n        kind, text = tokens[pos]\n        if kind == \"num\":\n            pos += 1\n            return Fraction(text) if compute else None\n        if text == \"(\":\n            pos += 1\n            value = parse_expr()\n            if peek_op() != \")\":\n                raise ValueError(\"Brak nawiasu zamykającego\")\n            pos += 1\n            return value\n        raise ValueError(\"Nieoczekiwany token: %r\" % text)\n\n    result = parse_expr()\n    if pos != n:\n        raise ValueError(\"Nieoczekiwany token: %r\" % (tokens[pos][1],))\n    return result\n\n\ndef evaluate(expr: str) -> Fraction:\n    if not isinstance(expr, str):\n        raise ValueError(\"Wyrażenie musi być napisem\")\n    tokens = _tokenize(expr)\n    if not tokens:\n        raise ValueError(\"Puste wyrażenie\")\n    _run(tokens, False)          # najpierw pełna kontrola składni\n    result = _run(tokens, True)  # potem dokładne obliczenie\n    return Fraction(result)\n",
   "tokeny": {
    "wej": 625,
    "wyj": 2323,
    "rozumowanie": 991
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "H2_slownie",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Napisz funkcję Pythona `kwota_slownie(zl: int, gr: int) -> str`, która zapisuje kwotę słownie tak jak na polskiej fakturze.\n\nSpecyfikacja:\n- zl od 0 do 999 999 999, gr od 0 do 99; poza zakresem ValueError.\n- Format wyniku: \"<liczba złotych słownie> <forma słowa złoty> <gr dwucyfrowo>/100\", wszystko małymi literami, pojedyncze spacje. Przykład: kwota_slownie(1234, 5) -> \"tysiąc dwieście trzydzieści cztery złote 05/100\".\n- Formy: złoty / złote / złotych, tysiąc / tysiące / tysięcy, milion / miliony / milionów - zgodnie z polską gramatyką liczebników (np. 2 złote, 5 złotych, 12 złotych, 22 złote, 21 złotych, 1001 złotych).\n- Gdy grupa tysięcy albo milionów jest równa dokładnie 1, piszemy samo „tysiąc” / „milion” bez „jeden” (1000 -> \"tysiąc\", 1 001 000 -> \"milion tysiąc\"). Grupy równe 0 pomijamy.\n- 0 zł -> \"zero złotych 00/100\". 1 zł -> \"jeden złoty 00/100\".\n- Liczebniki: dwieście, trzysta, czterysta, pięćset...; jedenaście... dziewiętnaście; dwadzieścia, trzydzieści, czterdzieści, pięćdziesiąt...\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\n_JEDNOSCI = [\n    \"\", \"jeden\", \"dwa\", \"trzy\", \"cztery\", \"pięć\", \"sześć\", \"siedem\",\n    \"osiem\", \"dziewięć\", \"dziesięć\", \"jedenaście\", \"dwanaście\",\n    \"trzynaście\", \"czternaście\", \"piętnaście\", \"szesnaście\",\n    \"siedemnaście\", \"osiemnaście\", \"dziewiętnaście\",\n]\n\n_DZIESIATKI = [\n    \"\", \"\", \"dwadzieścia\", \"trzydzieści\", \"czterdzieści\", \"pięćdziesiąt\",\n    \"sześćdziesiąt\", \"siedemdziesiąt\", \"osiemdziesiąt\", \"dziewięćdziesiąt\",\n]\n\n_SETKI = [\n    \"\", \"sto\", \"dwieście\", \"trzysta\", \"czterysta\", \"pięćset\", \"sześćset\",\n    \"siedemset\", \"osiemset\", \"dziewięćset\",\n]\n\n\ndef _trojka_slownie(n: int) -> list:\n    \"\"\"Zwraca listę słów dla liczby 0..999 (pusta lista dla 0).\"\"\"\n    slowa = []\n    s = n // 100\n    reszta = n % 100\n    if s:\n        slowa.append(_SETKI[s])\n    if reszta < 20:\n        if reszta:\n            slowa.append(_JEDNOSCI[reszta])\n    else:\n        slowa.append(_DZIESIATKI[reszta // 10])\n        if reszta % 10:\n            slowa.append(_JEDNOSCI[reszta % 10])\n    return slowa\n\n\ndef _forma(n: int, formy: tuple) -> str:\n    \"\"\"Wybiera formę rzeczownika: (pojedyncza, mnoga 2-4, mnoga 5+).\"\"\"\n    if n == 1:\n        return formy[0]\n    if n % 10 in (2, 3, 4) and n % 100 not in (12, 13, 14):\n        return formy[1]\n    return formy[2]\n\n\ndef kwota_slownie(zl: int, gr: int) -> str:\n    if isinstance(zl, bool) or isinstance(gr, bool):\n        raise ValueError(\"zl i gr muszą być liczbami całkowitymi\")\n    if not isinstance(zl, int) or not isinstance(gr, int):\n        raise ValueError(\"zl i gr muszą być liczbami całkowitymi\")\n    if not 0 <= zl <= 999_999_999:\n        raise ValueError(\"zl poza zakresem 0..999999999\")\n    if not 0 <= gr <= 99:\n        raise ValueError(\"gr poza zakresem 0..99\")\n\n    slowa = []\n    if zl == 0:\n        slowa.append(\"zero\")\n    else:\n        miliony = zl // 1_000_000\n        tysiace = (zl // 1000) % 1000\n        jednosci = zl % 1000\n\n        if miliony:\n            if miliony > 1:\n                slowa.extend(_trojka_slownie(miliony))\n            slowa.append(_forma(miliony, (\"milion\", \"miliony\", \"milionów\")))\n        if tysiace:\n            if tysiace > 1:\n                slowa.extend(_trojka_slownie(tysiace))\n            slowa.append(_forma(tysiace, (\"tysiąc\", \"tysiące\", \"tysięcy\")))\n        if jednosci:\n            slowa.extend(_trojka_slownie(jednosci))\n\n    slowa.append(_forma(zl, (\"złoty\", \"złote\", \"złotych\")))\n    return \"{} {:02d}/100\".format(\" \".join(slowa), gr)\n```",
   "czas_s": 11.0,
   "usage": {
    "prompt_tokens": 632,
    "completion_tokens": 1202,
    "total_tokens": 1834,
    "cost": 0.053136,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.053136,
     "upstream_inference_prompt_cost": 0.005056,
     "upstream_inference_completions_cost": 0.04808
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.053136,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.053136,
   "ocena": {
    "zaliczone": 27,
    "testy": 27,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "_JEDNOSCI = [\n    \"\", \"jeden\", \"dwa\", \"trzy\", \"cztery\", \"pięć\", \"sześć\", \"siedem\",\n    \"osiem\", \"dziewięć\", \"dziesięć\", \"jedenaście\", \"dwanaście\",\n    \"trzynaście\", \"czternaście\", \"piętnaście\", \"szesnaście\",\n    \"siedemnaście\", \"osiemnaście\", \"dziewiętnaście\",\n]\n\n_DZIESIATKI = [\n    \"\", \"\", \"dwadzieścia\", \"trzydzieści\", \"czterdzieści\", \"pięćdziesiąt\",\n    \"sześćdziesiąt\", \"siedemdziesiąt\", \"osiemdziesiąt\", \"dziewięćdziesiąt\",\n]\n\n_SETKI = [\n    \"\", \"sto\", \"dwieście\", \"trzysta\", \"czterysta\", \"pięćset\", \"sześćset\",\n    \"siedemset\", \"osiemset\", \"dziewięćset\",\n]\n\n\ndef _trojka_slownie(n: int) -> list:\n    \"\"\"Zwraca listę słów dla liczby 0..999 (pusta lista dla 0).\"\"\"\n    slowa = []\n    s = n // 100\n    reszta = n % 100\n    if s:\n        slowa.append(_SETKI[s])\n    if reszta < 20:\n        if reszta:\n            slowa.append(_JEDNOSCI[reszta])\n    else:\n        slowa.append(_DZIESIATKI[reszta // 10])\n        if reszta % 10:\n            slowa.append(_JEDNOSCI[reszta % 10])\n    return slowa\n\n\ndef _forma(n: int, formy: tuple) -> str:\n    \"\"\"Wybiera formę rzeczownika: (pojedyncza, mnoga 2-4, mnoga 5+).\"\"\"\n    if n == 1:\n        return formy[0]\n    if n % 10 in (2, 3, 4) and n % 100 not in (12, 13, 14):\n        return formy[1]\n    return formy[2]\n\n\ndef kwota_slownie(zl: int, gr: int) -> str:\n    if isinstance(zl, bool) or isinstance(gr, bool):\n        raise ValueError(\"zl i gr muszą być liczbami całkowitymi\")\n    if not isinstance(zl, int) or not isinstance(gr, int):\n        raise ValueError(\"zl i gr muszą być liczbami całkowitymi\")\n    if not 0 <= zl <= 999_999_999:\n        raise ValueError(\"zl poza zakresem 0..999999999\")\n    if not 0 <= gr <= 99:\n        raise ValueError(\"gr poza zakresem 0..99\")\n\n    slowa = []\n    if zl == 0:\n        slowa.append(\"zero\")\n    else:\n        miliony = zl // 1_000_000\n        tysiace = (zl // 1000) % 1000\n        jednosci = zl % 1000\n\n        if miliony:\n            if miliony > 1:\n                slowa.extend(_trojka_slownie(miliony))\n            slowa.append(_forma(miliony, (\"milion\", \"miliony\", \"milionów\")))\n        if tysiace:\n            if tysiace > 1:\n                slowa.extend(_trojka_slownie(tysiace))\n            slowa.append(_forma(tysiace, (\"tysiąc\", \"tysiące\", \"tysięcy\")))\n        if jednosci:\n            slowa.extend(_trojka_slownie(jednosci))\n\n    slowa.append(_forma(zl, (\"złoty\", \"złote\", \"złotych\")))\n    return \"{} {:02d}/100\".format(\" \".join(slowa), gr)\n",
   "tokeny": {
    "wej": 632,
    "wyj": 1202,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T1_dni_robocze",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Ile dni roboczych (poniedziałek-piątek, z wyłączeniem dni ustawowo wolnych od pracy w Polsce według stanu prawnego na listopad 2026) jest w roku kalendarzowym 2027? Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "**Tok rozumowania:**\n\n1. Rok 2027 nie jest przestępny (365 dni) i zaczyna się w piątek, więc ma 52 pełne tygodnie plus jeden dodatkowy piątek. Dni od poniedziałku do piątku jest 52 × 5 + 1 = 261.\n\n2. Dni ustawowo wolne w 2027 (z Wigilią, wolną od 2025 r.) i ich dni tygodnia:\n   - 1 stycznia – piątek (odpada)\n   - 6 stycznia – środa (odpada)\n   - Wielkanoc 28 marca – niedziela\n   - Poniedziałek Wielkanocny 29 marca – poniedziałek (odpada)\n   - 1 maja – sobota\n   - 3 maja – poniedziałek (odpada)\n   - Zielone Świątki 16 maja – niedziela\n   - Boże Ciało 27 maja – czwartek (odpada)\n   - 15 sierpnia – niedziela\n   - 1 listopada – poniedziałek (odpada)\n   - 11 listopada – czwartek (odpada)\n   - 24 grudnia – piątek (odpada)\n   - 25 grudnia – sobota\n   - 26 grudnia – niedziela\n\n3. Świąt przypadających od poniedziałku do piątku jest 8, więc 261 − 8 = 253.\n\nUwaga: liczę zgodnie z definicją z pytania, więc nie odejmuję dodatkowego dnia wolnego, który pracodawca musi oddać za święto w sobotę (1 maja i 25 grudnia).\n\nODPOWIEDŹ: 253",
   "czas_s": 12.36,
   "usage": {
    "prompt_tokens": 193,
    "completion_tokens": 1209,
    "total_tokens": 1402,
    "cost": 0.049904,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.049904,
     "upstream_inference_prompt_cost": 0.001544,
     "upstream_inference_completions_cost": 0.04836
    },
    "completion_tokens_details": {
     "reasoning_tokens": 642,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.049904,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.049904,
   "ocena": {
    "odp": "253",
    "ok": true
   },
   "tokeny": {
    "wej": 193,
    "wyj": 1209,
    "rozumowanie": 642
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T2_kajakarz",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Na ile różnych sposobów można ustawić wszystkie litery słowa KAJAKARZ tak, by żadne dwie litery A nie stały obok siebie? Ustawienia różniące się tylko zamianą identycznych liter traktujemy jako jedno. Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Słowo KAJAKARZ ma 8 liter: K, A, J, A, K, A, R, Z, czyli A×3, K×2, J, R, Z.\n\n1. Ustawiamy najpierw litery inne niż A: K, K, J, R, Z (5 liter, K powtarza się dwukrotnie): 5!/2! = 60 sposobów.\n\n2. Pięć liter tworzy 6 luk (wliczając końce). Wybieramy 3 różne luki na litery A, żeby żadne dwie nie sąsiadowały: C(6,3) = 20 sposobów.\n\n3. Razem: 60 · 20 = 1200.\n\nODPOWIEDŹ: 1200",
   "czas_s": 4.51,
   "usage": {
    "prompt_tokens": 203,
    "completion_tokens": 237,
    "total_tokens": 440,
    "cost": 0.011104,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.011104,
     "upstream_inference_prompt_cost": 0.001624,
     "upstream_inference_completions_cost": 0.00948
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.011104,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.011104,
   "ocena": {
    "odp": "1200",
    "ok": true
   },
   "tokeny": {
    "wej": 203,
    "wyj": 237,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T3_palindromy",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Ile jest pięciocyfrowych liczb pierwszych, które są palindromami (czytane od tyłu dają tę samą liczbę)? Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Liczba pięciocyfrowych palindromów ma postać abcba, gdzie a ∈ {1, 3, 7, 9} (inaczej liczba byłaby parzysta lub podzielna przez 5), co daje 4·10·10 = 400 kandydatów do sprawdzenia pod kątem pierwszości.\n\nNie przeprowadzałem tu pełnego przesiewania tych 400 liczb — wynik podaję z pamięci jako znany fakt (ciąg liczby palindromicznych liczb pierwszych wg liczby cyfr: 4 jednocyfrowe, 1 dwucyfrowa, 15 trzycyfrowych, 93 pięciocyfrowe, 668 siedmiocyfrowych).\n\nPięciocyfrowych palindromicznych liczb pierwszych jest 93.\n\nODPOWIEDŹ: 93",
   "czas_s": 4.48,
   "usage": {
    "prompt_tokens": 162,
    "completion_tokens": 394,
    "total_tokens": 556,
    "cost": 0.017056,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.017056,
     "upstream_inference_prompt_cost": 0.001296,
     "upstream_inference_completions_cost": 0.01576
    },
    "completion_tokens_details": {
     "reasoning_tokens": 91,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.017056,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.017056,
   "ocena": {
    "odp": "93",
    "ok": true
   },
   "tokeny": {
    "wej": 162,
    "wyj": 394,
    "rozumowanie": 91
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T4_monty",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Teleturniej: za jednymi z trzech drzwi jest samochód, za dwoma kozy. Wybierasz drzwi nr 1. Prowadzący NIE WIE, gdzie jest samochód: otwiera losowo jedne z dwóch pozostałych drzwi i przypadkiem jest tam koza. Jakie jest prawdopodobieństwo wygrania samochodu, jeśli teraz zmienisz wybór? Podaj dokładny ułamek.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "To wariant „Monty Fall\" – prowadzący nie zna położenia samochodu, więc fakt odsłonięcia kozy niesie inną informację niż w klasycznym problemie.\n\nRozumowanie (wybrałem drzwi nr 1):\n\n- Samochód za drzwiami 1 (prawd. 1/3): prowadzący zawsze odsłoni kozę (prawd. 1). Wkład: 1/3 · 1 = 1/3.\n- Samochód za jednymi z pozostałych drzwi (prawd. 2/3): prowadzący losowo odsłoni kozę z prawd. 1/2. Wkład: 2/3 · 1/2 = 1/3.\n\nP(odsłonięto kozę) = 1/3 + 1/3 = 2/3.\n\nP(samochód za pozostałymi nieotwartymi drzwiami | koza) = (1/3) / (2/3) = 1/2.\n\nZmiana wyboru daje więc wygraną z prawdopodobieństwem 1/2 (tak samo jak pozostanie przy swoim).\n\nODPOWIEDŹ: 1/2",
   "czas_s": 5.74,
   "usage": {
    "prompt_tokens": 268,
    "completion_tokens": 385,
    "total_tokens": 653,
    "cost": 0.017544,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.017544,
     "upstream_inference_prompt_cost": 0.002144,
     "upstream_inference_completions_cost": 0.0154
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.017544,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.017544,
   "ocena": {
    "odp": "1/2",
    "ok": true
   },
   "tokeny": {
    "wej": 268,
    "wyj": 385,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T5_tysiac_dni",
   "model": "anthropic/claude-fable-5.2",
   "provider": "Anthropic",
   "prompt": "Dzień 9 listopada 2026 to dzień numer 1. Jaka data przypada na dzień numer 1000? Podaj datę w formacie RRRR-MM-DD.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Dzień 1 = 2026-11-09, więc dzień 1000 = 2026-11-09 + 999 dni.\n\nLiczę etapami:\n- 2026-11-09 + 365 dni = 2027-11-09 (365)\n- + 366 dni = 2028-11-09 (bo po drodze 29 lutego 2028) → razem 731\n- Pozostało 999 − 731 = 268 dni od 2028-11-09.\n\nOd 2028-11-09:\n- do 30 listopada: 21 dni → 2028-11-30 (21)\n- grudzień: 31 → 52 (2028-12-31)\n- styczeń 2029: 31 → 83\n- luty: 28 → 111\n- marzec: 31 → 142\n- kwiecień: 30 → 172\n- maj: 31 → 203\n- czerwiec: 30 → 233\n- lipiec: 31 → 264 (2029-07-31)\n- jeszcze 4 dni → 2029-08-04\n\nODPOWIEDŹ: 2029-08-04",
   "czas_s": 4.46,
   "usage": {
    "prompt_tokens": 165,
    "completion_tokens": 348,
    "total_tokens": 513,
    "cost": 0.01524,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.01524,
     "upstream_inference_prompt_cost": 0.00132,
     "upstream_inference_completions_cost": 0.01392
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.01524,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.01524,
   "ocena": {
    "odp": "2029-08-04",
    "ok": true
   },
   "tokeny": {
    "wej": 165,
    "wyj": 348,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "H1_kalkulator",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Napisz funkcję Pythona `evaluate(expr: str) -> fractions.Fraction`, która dokładnie oblicza wyrażenie arytmetyczne.\n\nSpecyfikacja:\n- Liczby: całkowite (np. 42) albo dziesiętne z kropką i cyframi po obu stronach (np. 3.25). Zapisy \".5\", \"5.\", \"1.2.3\", \"1e3\" są błędne.\n- Operatory: + - * / ^ oraz nawiasy ( ). Spacje mogą stać między dowolnymi tokenami.\n- Priorytety od najwyższego: (1) potęgowanie ^, łączne prawostronnie; (2) jednoargumentowy minus/plus (prefiks, może się powtarzać, np. \"--3\"); (3) * i /, łączne lewostronnie; (4) + i -, łączne lewostronnie.\n  Zatem -2^2 = -4, 2^-1 = 1/2 (wykładnik może zaczynać się od unarnego minusa), 2^3^2 = 512.\n- Wykładnik musi mieć wartość całkowitą (po obliczeniu), inaczej ValueError. 0 do potęgi ujemnej oraz dzielenie przez zero: ZeroDivisionError.\n- Każde wyrażenie niepoprawne składniowo (puste, niedomknięte nawiasy, \"()\", dwie liczby obok siebie, operator na końcu, \"2**3\", nieznany znak): ValueError.\n- Wynik zawsze jako Fraction (dokładnie, bez floatów).\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nimport fractions\n\n\ndef evaluate(expr: str) -> fractions.Fraction:\n    if not isinstance(expr, str):\n        raise ValueError(\"Expression must be a string\")\n\n    tokens = []\n    i = 0\n    n = len(expr)\n\n    while i < n:\n        c = expr[i]\n        if c.isspace():\n            i += 1\n        elif c in \"+-*/^()\":\n            tokens.append((\"OP\", c))\n            i += 1\n        elif c.isdigit():\n            start = i\n            while i < n and expr[i].isdigit():\n                i += 1\n            if i < n and expr[i] == \".\":\n                i += 1\n                if i == n or not expr[i].isdigit():\n                    raise ValueError(\"Invalid number format\")\n                while i < n and expr[i].isdigit():\n                    i += 1\n                if i < n and expr[i] == \".\":\n                    raise ValueError(\"Invalid number format\")\n            tokens.append((\"NUM\", fractions.Fraction(expr[start:i])))\n        else:\n            raise ValueError(f\"Invalid character: {c}\")\n\n    if not tokens:\n        raise ValueError(\"Empty expression\")\n\n    pos = 0\n\n    def peek():\n        return tokens[pos] if pos < len(tokens) else (None, None)\n\n    def consume():\n        nonlocal pos\n        tok = tokens[pos]\n        pos += 1\n        return tok\n\n    def parse_expr():\n        val = parse_term()\n        while peek()[0] == \"OP\" and peek()[1] in (\"+\", \"-\"):\n            op = consume()[1]\n            right = parse_term()\n            if op == \"+\":\n                val += right\n            else:\n                val -= right\n        return val\n\n    def parse_term():\n        val = parse_unary()\n        while peek()[0] == \"OP\" and peek()[1] in (\"*\", \"/\"):\n            op = consume()[1]\n            right = parse_unary()\n            if op == \"*\":\n                val *= right\n            else:\n                if right == 0:\n                    raise ZeroDivisionError(\"Division by zero\")\n                val /= right\n        return val\n\n    def parse_unary():\n        tok_type, tok_val = peek()\n        if tok_type == \"OP\" and tok_val in (\"+\", \"-\"):\n            consume()\n            val = parse_unary()\n            return -val if tok_val == \"-\" else val\n        return parse_power()\n\n    def parse_power():\n        base = parse_primary()\n        tok_type, tok_val = peek()\n        if tok_type == \"OP\" and tok_val == \"^\":\n            consume()\n            exp = parse_unary()\n            if exp.denominator != 1:\n                raise ValueError(\"Exponent must be an integer\")\n            exp_int = exp.numerator\n            if base == 0 and exp_int < 0:\n                raise ZeroDivisionError(\n                    \"Zero cannot be raised to a negative power\"\n                )\n            return base**exp_int\n        return base\n\n    def parse_primary():\n        tok_type, tok_val = peek()\n        if tok_type == \"NUM\":\n            consume()\n            return tok_val\n        elif tok_type == \"OP\" and tok_val == \"(\":\n            consume()\n            if peek() == (\"OP\", \")\"):\n                raise ValueError(\"Empty parentheses\")\n            val = parse_expr()\n            if peek() != (\"OP\", \")\"):\n                raise ValueError(\"Unclosed parenthesis\")\n            consume()\n            return val\n        else:\n            raise ValueError(\"Unexpected token\")\n\n    result = parse_expr()\n    if pos < len(tokens):\n        raise ValueError(\"Unexpected trailing tokens\")\n\n    return result\n```",
   "czas_s": 13.99,
   "usage": {
    "prompt_tokens": 430,
    "completion_tokens": 887,
    "total_tokens": 1317,
    "cost": 0.00973,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.00973,
     "upstream_inference_prompt_cost": 0.00086,
     "upstream_inference_completions_cost": 0.00887
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.00973,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.00973,
   "ocena": {
    "zaliczone": 33,
    "testy": 33,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "import fractions\n\n\ndef evaluate(expr: str) -> fractions.Fraction:\n    if not isinstance(expr, str):\n        raise ValueError(\"Expression must be a string\")\n\n    tokens = []\n    i = 0\n    n = len(expr)\n\n    while i < n:\n        c = expr[i]\n        if c.isspace():\n            i += 1\n        elif c in \"+-*/^()\":\n            tokens.append((\"OP\", c))\n            i += 1\n        elif c.isdigit():\n            start = i\n            while i < n and expr[i].isdigit():\n                i += 1\n            if i < n and expr[i] == \".\":\n                i += 1\n                if i == n or not expr[i].isdigit():\n                    raise ValueError(\"Invalid number format\")\n                while i < n and expr[i].isdigit():\n                    i += 1\n                if i < n and expr[i] == \".\":\n                    raise ValueError(\"Invalid number format\")\n            tokens.append((\"NUM\", fractions.Fraction(expr[start:i])))\n        else:\n            raise ValueError(f\"Invalid character: {c}\")\n\n    if not tokens:\n        raise ValueError(\"Empty expression\")\n\n    pos = 0\n\n    def peek():\n        return tokens[pos] if pos < len(tokens) else (None, None)\n\n    def consume():\n        nonlocal pos\n        tok = tokens[pos]\n        pos += 1\n        return tok\n\n    def parse_expr():\n        val = parse_term()\n        while peek()[0] == \"OP\" and peek()[1] in (\"+\", \"-\"):\n            op = consume()[1]\n            right = parse_term()\n            if op == \"+\":\n                val += right\n            else:\n                val -= right\n        return val\n\n    def parse_term():\n        val = parse_unary()\n        while peek()[0] == \"OP\" and peek()[1] in (\"*\", \"/\"):\n            op = consume()[1]\n            right = parse_unary()\n            if op == \"*\":\n                val *= right\n            else:\n                if right == 0:\n                    raise ZeroDivisionError(\"Division by zero\")\n                val /= right\n        return val\n\n    def parse_unary():\n        tok_type, tok_val = peek()\n        if tok_type == \"OP\" and tok_val in (\"+\", \"-\"):\n            consume()\n            val = parse_unary()\n            return -val if tok_val == \"-\" else val\n        return parse_power()\n\n    def parse_power():\n        base = parse_primary()\n        tok_type, tok_val = peek()\n        if tok_type == \"OP\" and tok_val == \"^\":\n            consume()\n            exp = parse_unary()\n            if exp.denominator != 1:\n                raise ValueError(\"Exponent must be an integer\")\n            exp_int = exp.numerator\n            if base == 0 and exp_int < 0:\n                raise ZeroDivisionError(\n                    \"Zero cannot be raised to a negative power\"\n                )\n            return base**exp_int\n        return base\n\n    def parse_primary():\n        tok_type, tok_val = peek()\n        if tok_type == \"NUM\":\n            consume()\n            return tok_val\n        elif tok_type == \"OP\" and tok_val == \"(\":\n            consume()\n            if peek() == (\"OP\", \")\"):\n                raise ValueError(\"Empty parentheses\")\n            val = parse_expr()\n            if peek() != (\"OP\", \")\"):\n                raise ValueError(\"Unclosed parenthesis\")\n            consume()\n            return val\n        else:\n            raise ValueError(\"Unexpected token\")\n\n    result = parse_expr()\n    if pos < len(tokens):\n        raise ValueError(\"Unexpected trailing tokens\")\n\n    return result\n",
   "tokeny": {
    "wej": 430,
    "wyj": 887,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "H1_kalkulator",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Napisz funkcję Pythona `evaluate(expr: str) -> fractions.Fraction`, która dokładnie oblicza wyrażenie arytmetyczne.\n\nSpecyfikacja:\n- Liczby: całkowite (np. 42) albo dziesiętne z kropką i cyframi po obu stronach (np. 3.25). Zapisy \".5\", \"5.\", \"1.2.3\", \"1e3\" są błędne.\n- Operatory: + - * / ^ oraz nawiasy ( ). Spacje mogą stać między dowolnymi tokenami.\n- Priorytety od najwyższego: (1) potęgowanie ^, łączne prawostronnie; (2) jednoargumentowy minus/plus (prefiks, może się powtarzać, np. \"--3\"); (3) * i /, łączne lewostronnie; (4) + i -, łączne lewostronnie.\n  Zatem -2^2 = -4, 2^-1 = 1/2 (wykładnik może zaczynać się od unarnego minusa), 2^3^2 = 512.\n- Wykładnik musi mieć wartość całkowitą (po obliczeniu), inaczej ValueError. 0 do potęgi ujemnej oraz dzielenie przez zero: ZeroDivisionError.\n- Każde wyrażenie niepoprawne składniowo (puste, niedomknięte nawiasy, \"()\", dwie liczby obok siebie, operator na końcu, \"2**3\", nieznany znak): ValueError.\n- Wynik zawsze jako Fraction (dokładnie, bez floatów).\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nimport re\nfrom fractions import Fraction\n\n_NUMBER_RE = re.compile(r\"[0-9]+(?:\\.[0-9]+)?\")\n_OPERATORS = set(\"+-*/^()\")\n\n\ndef _tokenize(expr):\n    tokens = []\n    i = 0\n    n = len(expr)\n    while i < n:\n        ch = expr[i]\n        if ch in \" \\t\\r\\n\\f\\v\":\n            i += 1\n            continue\n        if ch in _OPERATORS:\n            tokens.append((\"op\", ch))\n            i += 1\n            continue\n        if \"0\" <= ch <= \"9\":\n            m = _NUMBER_RE.match(expr, i)\n            end = m.end()\n            # Odrzuć zapisy typu \"5.\" lub \"1.2.3\" (kropka zaraz po liczbie)\n            if end < n and expr[end] == \".\":\n                raise ValueError(\"Niepoprawny zapis liczby na pozycji %d\" % i)\n            tokens.append((\"num\", Fraction(m.group(0))))\n            i = end\n            continue\n        raise ValueError(\"Nieznany znak %r na pozycji %d\" % (ch, i))\n    return tokens\n\n\nclass _Parser:\n    def __init__(self, tokens):\n        self.tokens = tokens\n        self.pos = 0\n\n    def _peek(self):\n        if self.pos < len(self.tokens):\n            return self.tokens[self.pos]\n        return None\n\n    def _is_op(self, chars):\n        tok = self._peek()\n        return tok is not None and tok[0] == \"op\" and tok[1] in chars\n\n    def _advance(self):\n        tok = self._peek()\n        self.pos += 1\n        return tok\n\n    def parse(self):\n        if not self.tokens:\n            raise ValueError(\"Puste wyrażenie\")\n        value = self._expr()\n        if self._peek() is not None:\n            raise ValueError(\"Nieoczekiwany token: %r\" % (self._peek()[1],))\n        return value\n\n    # expr := term (('+'|'-') term)*\n    def _expr(self):\n        value = self._term()\n        while self._is_op(\"+-\"):\n            op = self._advance()[1]\n            right = self._term()\n            if op == \"+\":\n                value = value + right\n            else:\n                value = value - right\n        return value\n\n    # term := unary (('*'|'/') unary)*\n    def _term(self):\n        value = self._unary()\n        while self._is_op(\"*/\"):\n            op = self._advance()[1]\n            right = self._unary()\n            if op == \"*\":\n                value = value * right\n            else:\n                if right == 0:\n                    raise ZeroDivisionError(\"Dzielenie przez zero\")\n                value = value / right\n        return value\n\n    # unary := ('+'|'-') unary | power\n    def _unary(self):\n        if self._is_op(\"+-\"):\n            op = self._advance()[1]\n            value = self._unary()\n            return -value if op == \"-\" else value\n        return self._power()\n\n    # power := primary ('^' unary)?   (prawostronna łączność przez unary -> power)\n    def _power(self):\n        base = self._primary()\n        if self._is_op(\"^\"):\n            self._advance()\n            exponent = self._unary()\n            if exponent.denominator != 1:\n                raise ValueError(\"Wykładnik musi być liczbą całkowitą\")\n            e = exponent.numerator\n            if base == 0 and e < 0:\n                raise ZeroDivisionError(\"0 do potęgi ujemnej\")\n            result = base ** e\n            if not isinstance(result, Fraction):\n                result = Fraction(result)\n            return result\n        return base\n\n    # primary := number | '(' expr ')'\n    def _primary(self):\n        tok = self._peek()\n        if tok is None:\n            raise ValueError(\"Nieoczekiwany koniec wyrażenia\")\n        if tok[0] == \"num\":\n            self._advance()\n            return tok[1]\n        if tok[1] == \"(\":\n            self._advance()\n            value = self._expr()\n            if not self._is_op(\")\"):\n                raise ValueError(\"Niedomknięty nawias\")\n            self._advance()\n            return value\n        raise ValueError(\"Nieoczekiwany token: %r\" % (tok[1],))\n\n\ndef evaluate(expr: str) -> Fraction:\n    if not isinstance(expr, str):\n        raise TypeError(\"Wyrażenie musi być typu str\")\n    tokens = _tokenize(expr)\n    try:\n        result = _Parser(tokens).parse()\n    except RecursionError:\n        raise ValueError(\"Wyrażenie zbyt głęboko zagnieżdżone\")\n    return Fraction(result)\n```",
   "czas_s": 17.4,
   "usage": {
    "prompt_tokens": 625,
    "completion_tokens": 2153,
    "total_tokens": 2778,
    "cost": 0.04556,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.04556,
     "upstream_inference_prompt_cost": 0.0025,
     "upstream_inference_completions_cost": 0.04306
    },
    "completion_tokens_details": {
     "reasoning_tokens": 621,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.04556,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.04556,
   "ocena": {
    "zaliczone": 33,
    "testy": 33,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "import re\nfrom fractions import Fraction\n\n_NUMBER_RE = re.compile(r\"[0-9]+(?:\\.[0-9]+)?\")\n_OPERATORS = set(\"+-*/^()\")\n\n\ndef _tokenize(expr):\n    tokens = []\n    i = 0\n    n = len(expr)\n    while i < n:\n        ch = expr[i]\n        if ch in \" \\t\\r\\n\\f\\v\":\n            i += 1\n            continue\n        if ch in _OPERATORS:\n            tokens.append((\"op\", ch))\n            i += 1\n            continue\n        if \"0\" <= ch <= \"9\":\n            m = _NUMBER_RE.match(expr, i)\n            end = m.end()\n            # Odrzuć zapisy typu \"5.\" lub \"1.2.3\" (kropka zaraz po liczbie)\n            if end < n and expr[end] == \".\":\n                raise ValueError(\"Niepoprawny zapis liczby na pozycji %d\" % i)\n            tokens.append((\"num\", Fraction(m.group(0))))\n            i = end\n            continue\n        raise ValueError(\"Nieznany znak %r na pozycji %d\" % (ch, i))\n    return tokens\n\n\nclass _Parser:\n    def __init__(self, tokens):\n        self.tokens = tokens\n        self.pos = 0\n\n    def _peek(self):\n        if self.pos < len(self.tokens):\n            return self.tokens[self.pos]\n        return None\n\n    def _is_op(self, chars):\n        tok = self._peek()\n        return tok is not None and tok[0] == \"op\" and tok[1] in chars\n\n    def _advance(self):\n        tok = self._peek()\n        self.pos += 1\n        return tok\n\n    def parse(self):\n        if not self.tokens:\n            raise ValueError(\"Puste wyrażenie\")\n        value = self._expr()\n        if self._peek() is not None:\n            raise ValueError(\"Nieoczekiwany token: %r\" % (self._peek()[1],))\n        return value\n\n    # expr := term (('+'|'-') term)*\n    def _expr(self):\n        value = self._term()\n        while self._is_op(\"+-\"):\n            op = self._advance()[1]\n            right = self._term()\n            if op == \"+\":\n                value = value + right\n            else:\n                value = value - right\n        return value\n\n    # term := unary (('*'|'/') unary)*\n    def _term(self):\n        value = self._unary()\n        while self._is_op(\"*/\"):\n            op = self._advance()[1]\n            right = self._unary()\n            if op == \"*\":\n                value = value * right\n            else:\n                if right == 0:\n                    raise ZeroDivisionError(\"Dzielenie przez zero\")\n                value = value / right\n        return value\n\n    # unary := ('+'|'-') unary | power\n    def _unary(self):\n        if self._is_op(\"+-\"):\n            op = self._advance()[1]\n            value = self._unary()\n            return -value if op == \"-\" else value\n        return self._power()\n\n    # power := primary ('^' unary)?   (prawostronna łączność przez unary -> power)\n    def _power(self):\n        base = self._primary()\n        if self._is_op(\"^\"):\n            self._advance()\n            exponent = self._unary()\n            if exponent.denominator != 1:\n                raise ValueError(\"Wykładnik musi być liczbą całkowitą\")\n            e = exponent.numerator\n            if base == 0 and e < 0:\n                raise ZeroDivisionError(\"0 do potęgi ujemnej\")\n            result = base ** e\n            if not isinstance(result, Fraction):\n                result = Fraction(result)\n            return result\n        return base\n\n    # primary := number | '(' expr ')'\n    def _primary(self):\n        tok = self._peek()\n        if tok is None:\n            raise ValueError(\"Nieoczekiwany koniec wyrażenia\")\n        if tok[0] == \"num\":\n            self._advance()\n            return tok[1]\n        if tok[1] == \"(\":\n            self._advance()\n            value = self._expr()\n            if not self._is_op(\")\"):\n                raise ValueError(\"Niedomknięty nawias\")\n            self._advance()\n            return value\n        raise ValueError(\"Nieoczekiwany token: %r\" % (tok[1],))\n\n\ndef evaluate(expr: str) -> Fraction:\n    if not isinstance(expr, str):\n        raise TypeError(\"Wyrażenie musi być typu str\")\n    tokens = _tokenize(expr)\n    try:\n        result = _Parser(tokens).parse()\n    except RecursionError:\n        raise ValueError(\"Wyrażenie zbyt głęboko zagnieżdżone\")\n    return Fraction(result)\n",
   "tokeny": {
    "wej": 625,
    "wyj": 2153,
    "rozumowanie": 621
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "H1_kalkulator",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Napisz funkcję Pythona `evaluate(expr: str) -> fractions.Fraction`, która dokładnie oblicza wyrażenie arytmetyczne.\n\nSpecyfikacja:\n- Liczby: całkowite (np. 42) albo dziesiętne z kropką i cyframi po obu stronach (np. 3.25). Zapisy \".5\", \"5.\", \"1.2.3\", \"1e3\" są błędne.\n- Operatory: + - * / ^ oraz nawiasy ( ). Spacje mogą stać między dowolnymi tokenami.\n- Priorytety od najwyższego: (1) potęgowanie ^, łączne prawostronnie; (2) jednoargumentowy minus/plus (prefiks, może się powtarzać, np. \"--3\"); (3) * i /, łączne lewostronnie; (4) + i -, łączne lewostronnie.\n  Zatem -2^2 = -4, 2^-1 = 1/2 (wykładnik może zaczynać się od unarnego minusa), 2^3^2 = 512.\n- Wykładnik musi mieć wartość całkowitą (po obliczeniu), inaczej ValueError. 0 do potęgi ujemnej oraz dzielenie przez zero: ZeroDivisionError.\n- Każde wyrażenie niepoprawne składniowo (puste, niedomknięte nawiasy, \"()\", dwie liczby obok siebie, operator na końcu, \"2**3\", nieznany znak): ValueError.\n- Wynik zawsze jako Fraction (dokładnie, bez floatów).\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\nimport re\nfrom fractions import Fraction\n\n\n_TOKEN = re.compile(r\"[0-9]+(?:\\.[0-9]+)?|[+\\-*/^()]\")\n\n\ndef evaluate(expr: str) -> Fraction:\n    tokens = []\n    position = 0\n\n    while position < len(expr):\n        if expr[position].isspace():\n            position += 1\n            continue\n\n        match = _TOKEN.match(expr, position)\n        if match is None:\n            raise ValueError(f\"Niepoprawny znak na pozycji {position}\")\n\n        tokens.append(match.group())\n        position = match.end()\n\n    tokens.append(\"\")\n    index = 0\n\n    def parse_atom():\n        nonlocal index\n        token = tokens[index]\n\n        if token == \"(\":\n            index += 1\n            node = parse_sum()\n            if tokens[index] != \")\":\n                raise ValueError(\"Oczekiwano zamykającego nawiasu\")\n            index += 1\n            return node\n\n        if token and \"0\" <= token[0] <= \"9\":\n            index += 1\n            return (\"number\", Fraction(token))\n\n        raise ValueError(\"Oczekiwano liczby lub nawiasu otwierającego\")\n\n    def parse_power():\n        nonlocal index\n        node = parse_atom()\n        if tokens[index] == \"^\":\n            index += 1\n            node = (\"^\", node, parse_unary())\n        return node\n\n    def parse_unary():\n        nonlocal index\n        negative = False\n\n        while tokens[index] in (\"+\", \"-\"):\n            if tokens[index] == \"-\":\n                negative = not negative\n            index += 1\n\n        node = parse_power()\n        return (\"negate\", node) if negative else node\n\n    def parse_product():\n        nonlocal index\n        node = parse_unary()\n\n        while tokens[index] in (\"*\", \"/\"):\n            operator = tokens[index]\n            index += 1\n            node = (operator, node, parse_unary())\n\n        return node\n\n    def parse_sum():\n        nonlocal index\n        node = parse_product()\n\n        while tokens[index] in (\"+\", \"-\"):\n            operator = tokens[index]\n            index += 1\n            node = (operator, node, parse_product())\n\n        return node\n\n    # Najpierw sprawdzamy całą składnię, dopiero potem wykonujemy obliczenia.\n    tree = parse_sum()\n    if tokens[index] != \"\":\n        raise ValueError(\"Nieoczekiwany token po końcu wyrażenia\")\n\n    def calculate(node) -> Fraction:\n        operator = node[0]\n\n        if operator == \"number\":\n            return node[1]\n        if operator == \"negate\":\n            return -calculate(node[1])\n\n        left = calculate(node[1])\n        right = calculate(node[2])\n\n        if operator == \"+\":\n            return left + right\n        if operator == \"-\":\n            return left - right\n        if operator == \"*\":\n            return left * right\n        if operator == \"/\":\n            if right == 0:\n                raise ZeroDivisionError(\"Dzielenie przez zero\")\n            return left / right\n\n        if right.denominator != 1:\n            raise ValueError(\"Wykładnik musi być całkowity\")\n        if left == 0 and right.numerator < 0:\n            raise ZeroDivisionError(\"Zero do potęgi ujemnej\")\n        return left ** right.numerator\n\n    return calculate(tree)\n```",
   "czas_s": 18.31,
   "usage": {
    "prompt_tokens": 425,
    "completion_tokens": 1094,
    "total_tokens": 1519,
    "cost": 0.01179,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.01179,
     "upstream_inference_prompt_cost": 0.00085,
     "upstream_inference_completions_cost": 0.01094
    },
    "completion_tokens_details": {
     "reasoning_tokens": 365,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.01179,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.01179,
   "ocena": {
    "zaliczone": 33,
    "testy": 33,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "import re\nfrom fractions import Fraction\n\n\n_TOKEN = re.compile(r\"[0-9]+(?:\\.[0-9]+)?|[+\\-*/^()]\")\n\n\ndef evaluate(expr: str) -> Fraction:\n    tokens = []\n    position = 0\n\n    while position < len(expr):\n        if expr[position].isspace():\n            position += 1\n            continue\n\n        match = _TOKEN.match(expr, position)\n        if match is None:\n            raise ValueError(f\"Niepoprawny znak na pozycji {position}\")\n\n        tokens.append(match.group())\n        position = match.end()\n\n    tokens.append(\"\")\n    index = 0\n\n    def parse_atom():\n        nonlocal index\n        token = tokens[index]\n\n        if token == \"(\":\n            index += 1\n            node = parse_sum()\n            if tokens[index] != \")\":\n                raise ValueError(\"Oczekiwano zamykającego nawiasu\")\n            index += 1\n            return node\n\n        if token and \"0\" <= token[0] <= \"9\":\n            index += 1\n            return (\"number\", Fraction(token))\n\n        raise ValueError(\"Oczekiwano liczby lub nawiasu otwierającego\")\n\n    def parse_power():\n        nonlocal index\n        node = parse_atom()\n        if tokens[index] == \"^\":\n            index += 1\n            node = (\"^\", node, parse_unary())\n        return node\n\n    def parse_unary():\n        nonlocal index\n        negative = False\n\n        while tokens[index] in (\"+\", \"-\"):\n            if tokens[index] == \"-\":\n                negative = not negative\n            index += 1\n\n        node = parse_power()\n        return (\"negate\", node) if negative else node\n\n    def parse_product():\n        nonlocal index\n        node = parse_unary()\n\n        while tokens[index] in (\"*\", \"/\"):\n            operator = tokens[index]\n            index += 1\n            node = (operator, node, parse_unary())\n\n        return node\n\n    def parse_sum():\n        nonlocal index\n        node = parse_product()\n\n        while tokens[index] in (\"+\", \"-\"):\n            operator = tokens[index]\n            index += 1\n            node = (operator, node, parse_product())\n\n        return node\n\n    # Najpierw sprawdzamy całą składnię, dopiero potem wykonujemy obliczenia.\n    tree = parse_sum()\n    if tokens[index] != \"\":\n        raise ValueError(\"Nieoczekiwany token po końcu wyrażenia\")\n\n    def calculate(node) -> Fraction:\n        operator = node[0]\n\n        if operator == \"number\":\n            return node[1]\n        if operator == \"negate\":\n            return -calculate(node[1])\n\n        left = calculate(node[1])\n        right = calculate(node[2])\n\n        if operator == \"+\":\n            return left + right\n        if operator == \"-\":\n            return left - right\n        if operator == \"*\":\n            return left * right\n        if operator == \"/\":\n            if right == 0:\n                raise ZeroDivisionError(\"Dzielenie przez zero\")\n            return left / right\n\n        if right.denominator != 1:\n            raise ValueError(\"Wykładnik musi być całkowity\")\n        if left == 0 and right.numerator < 0:\n            raise ZeroDivisionError(\"Zero do potęgi ujemnej\")\n        return left ** right.numerator\n\n    return calculate(tree)\n",
   "tokeny": {
    "wej": 425,
    "wyj": 1094,
    "rozumowanie": 365
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "H2_slownie",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Napisz funkcję Pythona `kwota_slownie(zl: int, gr: int) -> str`, która zapisuje kwotę słownie tak jak na polskiej fakturze.\n\nSpecyfikacja:\n- zl od 0 do 999 999 999, gr od 0 do 99; poza zakresem ValueError.\n- Format wyniku: \"<liczba złotych słownie> <forma słowa złoty> <gr dwucyfrowo>/100\", wszystko małymi literami, pojedyncze spacje. Przykład: kwota_slownie(1234, 5) -> \"tysiąc dwieście trzydzieści cztery złote 05/100\".\n- Formy: złoty / złote / złotych, tysiąc / tysiące / tysięcy, milion / miliony / milionów - zgodnie z polską gramatyką liczebników (np. 2 złote, 5 złotych, 12 złotych, 22 złote, 21 złotych, 1001 złotych).\n- Gdy grupa tysięcy albo milionów jest równa dokładnie 1, piszemy samo „tysiąc” / „milion” bez „jeden” (1000 -> \"tysiąc\", 1 001 000 -> \"milion tysiąc\"). Grupy równe 0 pomijamy.\n- 0 zł -> \"zero złotych 00/100\". 1 zł -> \"jeden złoty 00/100\".\n- Liczebniki: dwieście, trzysta, czterysta, pięćset...; jedenaście... dziewiętnaście; dwadzieścia, trzydzieści, czterdzieści, pięćdziesiąt...\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\ndef kwota_slownie(zl: int, gr: int) -> str:\n    if isinstance(zl, bool) or isinstance(gr, bool):\n        raise ValueError(\"Argumenty muszą być liczbami całkowitymi.\")\n    if not isinstance(zl, int) or not isinstance(gr, int):\n        raise ValueError(\"Argumenty muszą być liczbami całkowitymi.\")\n    if not (0 <= zl <= 999_999_999) or not (0 <= gr <= 99):\n        raise ValueError(\"Wartości poza dozwolonym zakresem.\")\n\n    jednosci = [\n        \"\", \"jeden\", \"dwa\", \"trzy\", \"cztery\", \"pięć\", \"sześć\", \"siedem\", \"osiem\", \"dziewięć\"\n    ]\n    nastki = [\n        \"dziesięć\", \"jedenaście\", \"dwanaście\", \"trzynaście\", \"czternaście\",\n        \"piętnaście\", \"szesnaście\", \"siedemnaście\", \"osiemnaście\", \"dziewiętnaście\"\n    ]\n    dziesiatki = [\n        \"\", \"dziesięć\", \"dwadzieścia\", \"trzydzieści\", \"czterdzieści\",\n        \"pięćdziesiąt\", \"sześćdziesiąt\", \"siedemdziesiąt\", \"osiemdziesiąt\", \"dziewięćdziesiąt\"\n    ]\n    setki = [\n        \"\", \"sto\", \"dwieście\", \"trzysta\", \"czterysta\",\n        \"pięćset\", \"sześćset\", \"siedemset\", \"osiemset\", \"dziewięćset\"\n    ]\n\n    def odmiana(n: int, formy: tuple[str, str, str]) -> str:\n        if n == 1:\n            return formy[0]\n        if 10 <= n % 100 <= 20:\n            return formy[2]\n        if n % 10 in (2, 3, 4):\n            return formy[1]\n        return formy[2]\n\n    def trzy_cyfry_slownie(n: int) -> list[str]:\n        slowa = []\n        s = n // 100\n        d = (n % 100) // 10\n        j = n % 10\n\n        if s > 0:\n            slowa.append(setki[s])\n        if d == 1:\n            slowa.append(nastki[j])\n        else:\n            if d > 1:\n                slowa.append(dziesiatki[d])\n            if j > 0:\n                slowa.append(jednosci[j])\n        return slowa\n\n    if zl == 0:\n        zl_slownie = [\"zero\"]\n    else:\n        zl_slownie = []\n        miliony = zl // 1_000_000\n        tysiace = (zl // 1_000) % 1_000\n        reszta = zl % 1_000\n\n        if miliony > 0:\n            if miliony == 1:\n                zl_slownie.append(\"milion\")\n            else:\n                zl_slownie.extend(trzy_cyfry_slownie(miliony))\n                zl_slownie.append(odmiana(miliony, (\"milion\", \"miliony\", \"milionów\")))\n\n        if tysiace > 0:\n            if tysiace == 1:\n                zl_slownie.append(\"tysiąc\")\n            else:\n                zl_slownie.extend(trzy_cyfry_slownie(tysiace))\n                zl_slownie.append(odmiana(tysiace, (\"tysiąc\", \"tysiące\", \"tysięcy\")))\n\n        if reszta > 0:\n            zl_slownie.extend(trzy_cyfry_slownie(reszta))\n\n    waluta = odmiana(zl, (\"złoty\", \"złote\", \"złotych\"))\n    zl_tekst = \" \".join(zl_slownie)\n\n    return f\"{zl_tekst} {waluta} {gr:02d}/100\"\n```",
   "czas_s": 14.49,
   "usage": {
    "prompt_tokens": 466,
    "completion_tokens": 1005,
    "total_tokens": 1471,
    "cost": 0.010982,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.010982,
     "upstream_inference_prompt_cost": 0.000932,
     "upstream_inference_completions_cost": 0.01005
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.010982,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.010982,
   "ocena": {
    "zaliczone": 27,
    "testy": 27,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "def kwota_slownie(zl: int, gr: int) -> str:\n    if isinstance(zl, bool) or isinstance(gr, bool):\n        raise ValueError(\"Argumenty muszą być liczbami całkowitymi.\")\n    if not isinstance(zl, int) or not isinstance(gr, int):\n        raise ValueError(\"Argumenty muszą być liczbami całkowitymi.\")\n    if not (0 <= zl <= 999_999_999) or not (0 <= gr <= 99):\n        raise ValueError(\"Wartości poza dozwolonym zakresem.\")\n\n    jednosci = [\n        \"\", \"jeden\", \"dwa\", \"trzy\", \"cztery\", \"pięć\", \"sześć\", \"siedem\", \"osiem\", \"dziewięć\"\n    ]\n    nastki = [\n        \"dziesięć\", \"jedenaście\", \"dwanaście\", \"trzynaście\", \"czternaście\",\n        \"piętnaście\", \"szesnaście\", \"siedemnaście\", \"osiemnaście\", \"dziewiętnaście\"\n    ]\n    dziesiatki = [\n        \"\", \"dziesięć\", \"dwadzieścia\", \"trzydzieści\", \"czterdzieści\",\n        \"pięćdziesiąt\", \"sześćdziesiąt\", \"siedemdziesiąt\", \"osiemdziesiąt\", \"dziewięćdziesiąt\"\n    ]\n    setki = [\n        \"\", \"sto\", \"dwieście\", \"trzysta\", \"czterysta\",\n        \"pięćset\", \"sześćset\", \"siedemset\", \"osiemset\", \"dziewięćset\"\n    ]\n\n    def odmiana(n: int, formy: tuple[str, str, str]) -> str:\n        if n == 1:\n            return formy[0]\n        if 10 <= n % 100 <= 20:\n            return formy[2]\n        if n % 10 in (2, 3, 4):\n            return formy[1]\n        return formy[2]\n\n    def trzy_cyfry_slownie(n: int) -> list[str]:\n        slowa = []\n        s = n // 100\n        d = (n % 100) // 10\n        j = n % 10\n\n        if s > 0:\n            slowa.append(setki[s])\n        if d == 1:\n            slowa.append(nastki[j])\n        else:\n            if d > 1:\n                slowa.append(dziesiatki[d])\n            if j > 0:\n                slowa.append(jednosci[j])\n        return slowa\n\n    if zl == 0:\n        zl_slownie = [\"zero\"]\n    else:\n        zl_slownie = []\n        miliony = zl // 1_000_000\n        tysiace = (zl // 1_000) % 1_000\n        reszta = zl % 1_000\n\n        if miliony > 0:\n            if miliony == 1:\n                zl_slownie.append(\"milion\")\n            else:\n                zl_slownie.extend(trzy_cyfry_slownie(miliony))\n                zl_slownie.append(odmiana(miliony, (\"milion\", \"miliony\", \"milionów\")))\n\n        if tysiace > 0:\n            if tysiace == 1:\n                zl_slownie.append(\"tysiąc\")\n            else:\n                zl_slownie.extend(trzy_cyfry_slownie(tysiace))\n                zl_slownie.append(odmiana(tysiace, (\"tysiąc\", \"tysiące\", \"tysięcy\")))\n\n        if reszta > 0:\n            zl_slownie.extend(trzy_cyfry_slownie(reszta))\n\n    waluta = odmiana(zl, (\"złoty\", \"złote\", \"złotych\"))\n    zl_tekst = \" \".join(zl_slownie)\n\n    return f\"{zl_tekst} {waluta} {gr:02d}/100\"\n",
   "tokeny": {
    "wej": 466,
    "wyj": 1005,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "H2_slownie",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Napisz funkcję Pythona `kwota_slownie(zl: int, gr: int) -> str`, która zapisuje kwotę słownie tak jak na polskiej fakturze.\n\nSpecyfikacja:\n- zl od 0 do 999 999 999, gr od 0 do 99; poza zakresem ValueError.\n- Format wyniku: \"<liczba złotych słownie> <forma słowa złoty> <gr dwucyfrowo>/100\", wszystko małymi literami, pojedyncze spacje. Przykład: kwota_slownie(1234, 5) -> \"tysiąc dwieście trzydzieści cztery złote 05/100\".\n- Formy: złoty / złote / złotych, tysiąc / tysiące / tysięcy, milion / miliony / milionów - zgodnie z polską gramatyką liczebników (np. 2 złote, 5 złotych, 12 złotych, 22 złote, 21 złotych, 1001 złotych).\n- Gdy grupa tysięcy albo milionów jest równa dokładnie 1, piszemy samo „tysiąc” / „milion” bez „jeden” (1000 -> \"tysiąc\", 1 001 000 -> \"milion tysiąc\"). Grupy równe 0 pomijamy.\n- 0 zł -> \"zero złotych 00/100\". 1 zł -> \"jeden złoty 00/100\".\n- Liczebniki: dwieście, trzysta, czterysta, pięćset...; jedenaście... dziewiętnaście; dwadzieścia, trzydzieści, czterdzieści, pięćdziesiąt...\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\ndef kwota_slownie(zl: int, gr: int) -> str:\n    if not isinstance(zl, int) or not 0 <= zl <= 999_999_999:\n        raise ValueError(\"zl musi być liczbą całkowitą od 0 do 999 999 999\")\n    if not isinstance(gr, int) or not 0 <= gr <= 99:\n        raise ValueError(\"gr musi być liczbą całkowitą od 0 do 99\")\n\n    jednosci = (\n        \"\", \"jeden\", \"dwa\", \"trzy\", \"cztery\",\n        \"pięć\", \"sześć\", \"siedem\", \"osiem\", \"dziewięć\",\n    )\n    nastki = (\n        \"dziesięć\", \"jedenaście\", \"dwanaście\", \"trzynaście\",\n        \"czternaście\", \"piętnaście\", \"szesnaście\", \"siedemnaście\",\n        \"osiemnaście\", \"dziewiętnaście\",\n    )\n    dziesiatki = (\n        \"\", \"\", \"dwadzieścia\", \"trzydzieści\", \"czterdzieści\",\n        \"pięćdziesiąt\", \"sześćdziesiąt\", \"siedemdziesiąt\",\n        \"osiemdziesiąt\", \"dziewięćdziesiąt\",\n    )\n    setki = (\n        \"\", \"sto\", \"dwieście\", \"trzysta\", \"czterysta\",\n        \"pięćset\", \"sześćset\", \"siedemset\", \"osiemset\", \"dziewięćset\",\n    )\n\n    def do_999(n: int) -> str:\n        slowa = []\n        s, reszta = divmod(n, 100)\n        if s:\n            slowa.append(setki[s])\n        if 10 <= reszta <= 19:\n            slowa.append(nastki[reszta - 10])\n        else:\n            d, j = divmod(reszta, 10)\n            if d:\n                slowa.append(dziesiatki[d])\n            if j:\n                slowa.append(jednosci[j])\n        return \" \".join(slowa)\n\n    def forma(n: int, pojedyncza: str, mnoga: str, dopelniacz: str) -> str:\n        if n == 1:\n            return pojedyncza\n        if 2 <= n % 10 <= 4 and not 12 <= n % 100 <= 14:\n            return mnoga\n        return dopelniacz\n\n    slowa = []\n    miliony, reszta = divmod(zl, 1_000_000)\n    tysiace, jednostki = divmod(reszta, 1_000)\n\n    for grupa, formy in (\n        (miliony, (\"milion\", \"miliony\", \"milionów\")),\n        (tysiace, (\"tysiąc\", \"tysiące\", \"tysięcy\")),\n    ):\n        if grupa:\n            if grupa != 1:\n                slowa.append(do_999(grupa))\n            slowa.append(forma(grupa, *formy))\n\n    if jednostki:\n        slowa.append(do_999(jednostki))\n    elif zl == 0:\n        slowa.append(\"zero\")\n\n    slowa.append(forma(zl, \"złoty\", \"złote\", \"złotych\"))\n    slowa.append(f\"{gr:02d}/100\")\n    return \" \".join(slowa)\n```",
   "czas_s": 11.56,
   "usage": {
    "prompt_tokens": 450,
    "completion_tokens": 874,
    "total_tokens": 1324,
    "cost": 0.00964,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.00964,
     "upstream_inference_prompt_cost": 0.0009,
     "upstream_inference_completions_cost": 0.00874
    },
    "completion_tokens_details": {
     "reasoning_tokens": 99,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.00964,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.00964,
   "ocena": {
    "zaliczone": 27,
    "testy": 27,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "def kwota_slownie(zl: int, gr: int) -> str:\n    if not isinstance(zl, int) or not 0 <= zl <= 999_999_999:\n        raise ValueError(\"zl musi być liczbą całkowitą od 0 do 999 999 999\")\n    if not isinstance(gr, int) or not 0 <= gr <= 99:\n        raise ValueError(\"gr musi być liczbą całkowitą od 0 do 99\")\n\n    jednosci = (\n        \"\", \"jeden\", \"dwa\", \"trzy\", \"cztery\",\n        \"pięć\", \"sześć\", \"siedem\", \"osiem\", \"dziewięć\",\n    )\n    nastki = (\n        \"dziesięć\", \"jedenaście\", \"dwanaście\", \"trzynaście\",\n        \"czternaście\", \"piętnaście\", \"szesnaście\", \"siedemnaście\",\n        \"osiemnaście\", \"dziewiętnaście\",\n    )\n    dziesiatki = (\n        \"\", \"\", \"dwadzieścia\", \"trzydzieści\", \"czterdzieści\",\n        \"pięćdziesiąt\", \"sześćdziesiąt\", \"siedemdziesiąt\",\n        \"osiemdziesiąt\", \"dziewięćdziesiąt\",\n    )\n    setki = (\n        \"\", \"sto\", \"dwieście\", \"trzysta\", \"czterysta\",\n        \"pięćset\", \"sześćset\", \"siedemset\", \"osiemset\", \"dziewięćset\",\n    )\n\n    def do_999(n: int) -> str:\n        slowa = []\n        s, reszta = divmod(n, 100)\n        if s:\n            slowa.append(setki[s])\n        if 10 <= reszta <= 19:\n            slowa.append(nastki[reszta - 10])\n        else:\n            d, j = divmod(reszta, 10)\n            if d:\n                slowa.append(dziesiatki[d])\n            if j:\n                slowa.append(jednosci[j])\n        return \" \".join(slowa)\n\n    def forma(n: int, pojedyncza: str, mnoga: str, dopelniacz: str) -> str:\n        if n == 1:\n            return pojedyncza\n        if 2 <= n % 10 <= 4 and not 12 <= n % 100 <= 14:\n            return mnoga\n        return dopelniacz\n\n    slowa = []\n    miliony, reszta = divmod(zl, 1_000_000)\n    tysiace, jednostki = divmod(reszta, 1_000)\n\n    for grupa, formy in (\n        (miliony, (\"milion\", \"miliony\", \"milionów\")),\n        (tysiace, (\"tysiąc\", \"tysiące\", \"tysięcy\")),\n    ):\n        if grupa:\n            if grupa != 1:\n                slowa.append(do_999(grupa))\n            slowa.append(forma(grupa, *formy))\n\n    if jednostki:\n        slowa.append(do_999(jednostki))\n    elif zl == 0:\n        slowa.append(\"zero\")\n\n    slowa.append(forma(zl, \"złoty\", \"złote\", \"złotych\"))\n    slowa.append(f\"{gr:02d}/100\")\n    return \" \".join(slowa)\n",
   "tokeny": {
    "wej": 450,
    "wyj": 874,
    "rozumowanie": 99
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "H2_slownie",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Napisz funkcję Pythona `kwota_slownie(zl: int, gr: int) -> str`, która zapisuje kwotę słownie tak jak na polskiej fakturze.\n\nSpecyfikacja:\n- zl od 0 do 999 999 999, gr od 0 do 99; poza zakresem ValueError.\n- Format wyniku: \"<liczba złotych słownie> <forma słowa złoty> <gr dwucyfrowo>/100\", wszystko małymi literami, pojedyncze spacje. Przykład: kwota_slownie(1234, 5) -> \"tysiąc dwieście trzydzieści cztery złote 05/100\".\n- Formy: złoty / złote / złotych, tysiąc / tysiące / tysięcy, milion / miliony / milionów - zgodnie z polską gramatyką liczebników (np. 2 złote, 5 złotych, 12 złotych, 22 złote, 21 złotych, 1001 złotych).\n- Gdy grupa tysięcy albo milionów jest równa dokładnie 1, piszemy samo „tysiąc” / „milion” bez „jeden” (1000 -> \"tysiąc\", 1 001 000 -> \"milion tysiąc\"). Grupy równe 0 pomijamy.\n- 0 zł -> \"zero złotych 00/100\". 1 zł -> \"jeden złoty 00/100\".\n- Liczebniki: dwieście, trzysta, czterysta, pięćset...; jedenaście... dziewiętnaście; dwadzieścia, trzydzieści, czterdzieści, pięćdziesiąt...\n\nZwróć wyłącznie kompletny kod w jednym bloku ```python``` (bez przykładów użycia, bez input(), bez print na poziomie modułu). Możesz używać tylko biblioteki standardowej.",
   "odpowiedz": "```python\ndef kwota_slownie(zl: int, gr: int) -> str:\n    for nazwa, wartosc, maks in ((\"zl\", zl, 999_999_999), (\"gr\", gr, 99)):\n        if isinstance(wartosc, bool) or not isinstance(wartosc, int):\n            raise ValueError(f\"{nazwa} musi być liczbą całkowitą\")\n        if wartosc < 0 or wartosc > maks:\n            raise ValueError(f\"{nazwa} poza zakresem 0..{maks}\")\n\n    jednosci = [\"\", \"jeden\", \"dwa\", \"trzy\", \"cztery\", \"pięć\",\n                \"sześć\", \"siedem\", \"osiem\", \"dziewięć\"]\n    nastki = [\"dziesięć\", \"jedenaście\", \"dwanaście\", \"trzynaście\", \"czternaście\",\n              \"piętnaście\", \"szesnaście\", \"siedemnaście\", \"osiemnaście\",\n              \"dziewiętnaście\"]\n    dziesiatki = [\"\", \"\", \"dwadzieścia\", \"trzydzieści\", \"czterdzieści\",\n                  \"pięćdziesiąt\", \"sześćdziesiąt\", \"siedemdziesiąt\",\n                  \"osiemdziesiąt\", \"dziewięćdziesiąt\"]\n    setki = [\"\", \"sto\", \"dwieście\", \"trzysta\", \"czterysta\", \"pięćset\",\n             \"sześćset\", \"siedemset\", \"osiemset\", \"dziewięćset\"]\n\n    def trzy_cyfry(n: int) -> list:\n        slowa = []\n        s, reszta = divmod(n, 100)\n        if s:\n            slowa.append(setki[s])\n        if 10 <= reszta <= 19:\n            slowa.append(nastki[reszta - 10])\n        else:\n            d, j = divmod(reszta, 10)\n            if d:\n                slowa.append(dziesiatki[d])\n            if j:\n                slowa.append(jednosci[j])\n        return slowa\n\n    def forma(n: int, poj: str, mn: str, dop: str) -> str:\n        if n == 1:\n            return poj\n        if 2 <= n % 10 <= 4 and not (12 <= n % 100 <= 14):\n            return mn\n        return dop\n\n    slowa = []\n    if zl == 0:\n        slowa.append(\"zero\")\n    else:\n        miliony, reszta = divmod(zl, 1_000_000)\n        tysiace, jedn = divmod(reszta, 1000)\n\n        if miliony:\n            if miliony == 1:\n                slowa.append(\"milion\")\n            else:\n                slowa.extend(trzy_cyfry(miliony))\n                slowa.append(forma(miliony, \"milion\", \"miliony\", \"milionów\"))\n        if tysiace:\n            if tysiace == 1:\n                slowa.append(\"tysiąc\")\n            else:\n                slowa.extend(trzy_cyfry(tysiace))\n                slowa.append(forma(tysiace, \"tysiąc\", \"tysiące\", \"tysięcy\"))\n        if jedn:\n            slowa.extend(trzy_cyfry(jedn))\n\n    slowa.append(forma(zl, \"złoty\", \"złote\", \"złotych\"))\n    slowa.append(f\"{gr:02d}/100\")\n    return \" \".join(slowa)\n```",
   "czas_s": 12.97,
   "usage": {
    "prompt_tokens": 632,
    "completion_tokens": 1550,
    "total_tokens": 2182,
    "cost": 0.033528,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.033528,
     "upstream_inference_prompt_cost": 0.002528,
     "upstream_inference_completions_cost": 0.031
    },
    "completion_tokens_details": {
     "reasoning_tokens": 450,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.033528,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.033528,
   "ocena": {
    "zaliczone": 27,
    "testy": 27,
    "blad": null,
    "niezaliczone": []
   },
   "kod": "def kwota_slownie(zl: int, gr: int) -> str:\n    for nazwa, wartosc, maks in ((\"zl\", zl, 999_999_999), (\"gr\", gr, 99)):\n        if isinstance(wartosc, bool) or not isinstance(wartosc, int):\n            raise ValueError(f\"{nazwa} musi być liczbą całkowitą\")\n        if wartosc < 0 or wartosc > maks:\n            raise ValueError(f\"{nazwa} poza zakresem 0..{maks}\")\n\n    jednosci = [\"\", \"jeden\", \"dwa\", \"trzy\", \"cztery\", \"pięć\",\n                \"sześć\", \"siedem\", \"osiem\", \"dziewięć\"]\n    nastki = [\"dziesięć\", \"jedenaście\", \"dwanaście\", \"trzynaście\", \"czternaście\",\n              \"piętnaście\", \"szesnaście\", \"siedemnaście\", \"osiemnaście\",\n              \"dziewiętnaście\"]\n    dziesiatki = [\"\", \"\", \"dwadzieścia\", \"trzydzieści\", \"czterdzieści\",\n                  \"pięćdziesiąt\", \"sześćdziesiąt\", \"siedemdziesiąt\",\n                  \"osiemdziesiąt\", \"dziewięćdziesiąt\"]\n    setki = [\"\", \"sto\", \"dwieście\", \"trzysta\", \"czterysta\", \"pięćset\",\n             \"sześćset\", \"siedemset\", \"osiemset\", \"dziewięćset\"]\n\n    def trzy_cyfry(n: int) -> list:\n        slowa = []\n        s, reszta = divmod(n, 100)\n        if s:\n            slowa.append(setki[s])\n        if 10 <= reszta <= 19:\n            slowa.append(nastki[reszta - 10])\n        else:\n            d, j = divmod(reszta, 10)\n            if d:\n                slowa.append(dziesiatki[d])\n            if j:\n                slowa.append(jednosci[j])\n        return slowa\n\n    def forma(n: int, poj: str, mn: str, dop: str) -> str:\n        if n == 1:\n            return poj\n        if 2 <= n % 10 <= 4 and not (12 <= n % 100 <= 14):\n            return mn\n        return dop\n\n    slowa = []\n    if zl == 0:\n        slowa.append(\"zero\")\n    else:\n        miliony, reszta = divmod(zl, 1_000_000)\n        tysiace, jedn = divmod(reszta, 1000)\n\n        if miliony:\n            if miliony == 1:\n                slowa.append(\"milion\")\n            else:\n                slowa.extend(trzy_cyfry(miliony))\n                slowa.append(forma(miliony, \"milion\", \"miliony\", \"milionów\"))\n        if tysiace:\n            if tysiace == 1:\n                slowa.append(\"tysiąc\")\n            else:\n                slowa.extend(trzy_cyfry(tysiace))\n                slowa.append(forma(tysiace, \"tysiąc\", \"tysiące\", \"tysięcy\"))\n        if jedn:\n            slowa.extend(trzy_cyfry(jedn))\n\n    slowa.append(forma(zl, \"złoty\", \"złote\", \"złotych\"))\n    slowa.append(f\"{gr:02d}/100\")\n    return \" \".join(slowa)\n",
   "tokeny": {
    "wej": 632,
    "wyj": 1550,
    "rozumowanie": 450
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T1_dni_robocze",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Ile dni roboczych (poniedziałek-piątek, z wyłączeniem dni ustawowo wolnych od pracy w Polsce według stanu prawnego na listopad 2026) jest w roku kalendarzowym 2027? Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "W 2027 r. jest 261 dni od poniedziałku do piątku. Spośród świąt ustawowo wolnych od pracy 8 przypada w te dni: 1 i 6 stycznia, 29 marca, 3 i 27 maja, 1 i 11 listopada oraz 24 grudnia (Wigilia).\n\n261 − 8 = 253.\n\nODPOWIEDŹ: 253",
   "czas_s": 12.92,
   "usage": {
    "prompt_tokens": 121,
    "completion_tokens": 417,
    "total_tokens": 538,
    "cost": 0.004412,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.004412,
     "upstream_inference_prompt_cost": 0.000242,
     "upstream_inference_completions_cost": 0.00417
    },
    "completion_tokens_details": {
     "reasoning_tokens": 319,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.004412,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.004412,
   "ocena": {
    "odp": "253",
    "ok": true
   },
   "tokeny": {
    "wej": 121,
    "wyj": 417,
    "rozumowanie": 319
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T1_dni_robocze",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Ile dni roboczych (poniedziałek-piątek, z wyłączeniem dni ustawowo wolnych od pracy w Polsce według stanu prawnego na listopad 2026) jest w roku kalendarzowym 2027? Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "**Liczba dni roboczych w 2027 r. w Polsce: 253.**\n\n**1. Dni od poniedziałku do piątku w 2027 roku**\n\nRok 2027 nie jest przestępny i ma 365 dni, czyli 52 pełne tygodnie i 1 dzień. Zaczyna się w piątek, więc piątków jest 53. Dni poniedziałek–piątek jest zatem 52 × 5 + 1 = 261.\n\n**2. Święta ustawowe przypadające w dni robocze**\n\nWielkanoc 2027 przypada 28 marca, więc Zielone Świątki wypadają 16 maja, a Boże Ciało 27 maja. Od 2025 r. Wigilia (24 grudnia) jest dniem wolnym od pracy.\n\n| Święto | Data | Dzień tygodnia |\n|---|---|---|\n| Nowy Rok | 1.01 | piątek |\n| Trzech Króli | 6.01 | środa |\n| Poniedziałek Wielkanocny | 29.03 | poniedziałek |\n| Święto Konstytucji 3 Maja | 3.05 | poniedziałek |\n| Boże Ciało | 27.05 | czwartek |\n| Wszystkich Świętych | 1.11 | poniedziałek |\n| Święto Niepodległości | 11.11 | czwartek |\n| Wigilia | 24.12 | piątek |\n\nTo daje 8 świąt w dni robocze.\n\nPozostałe święta wypadają w weekend, więc nie zmniejszają liczby dni roboczych:\n- 1.05 – sobota,\n- 15.08 – niedziela,\n- 25.12 – sobota,\n- 26.12 – niedziela,\n- Wielkanoc i Zielone Świątki – zawsze niedziela.\n\n**3. Wynik**\n\n261 − 8 = 253\n\nODPOWIEDŹ: 253",
   "czas_s": 12.49,
   "usage": {
    "prompt_tokens": 193,
    "completion_tokens": 1388,
    "total_tokens": 1581,
    "cost": 0.028532,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.028532,
     "upstream_inference_prompt_cost": 0.000772,
     "upstream_inference_completions_cost": 0.02776
    },
    "completion_tokens_details": {
     "reasoning_tokens": 735,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.028532,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.028532,
   "ocena": {
    "odp": "253",
    "ok": true
   },
   "tokeny": {
    "wej": 193,
    "wyj": 1388,
    "rozumowanie": 735
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T2_kajakarz",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Na ile różnych sposobów można ustawić wszystkie litery słowa KAJAKARZ tak, by żadne dwie litery A nie stały obok siebie? Ustawienia różniące się tylko zamianą identycznych liter traktujemy jako jedno. Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Najpierw ustawiamy 5 liter innych niż A: K, K, J, R, Z. Można to zrobić na \\(\\frac{5!}{2!}=60\\) sposobów.\n\nWokół tych liter jest 6 miejsc (przed, pomiędzy i za nimi). Aby litery A nie sąsiadowały, wybieramy 3 różne miejsca spośród 6, czyli \\(\\binom{6}{3}=20\\) sposobów.\n\nŁącznie: \\(60\\cdot20=1200\\).\n\nODPOWIEDŹ: 1200",
   "czas_s": 4.36,
   "usage": {
    "prompt_tokens": 124,
    "completion_tokens": 175,
    "total_tokens": 299,
    "cost": 0.001998,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.001998,
     "upstream_inference_prompt_cost": 0.000248,
     "upstream_inference_completions_cost": 0.00175
    },
    "completion_tokens_details": {
     "reasoning_tokens": 41,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.001998,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.001998,
   "ocena": {
    "odp": "1200",
    "ok": true
   },
   "tokeny": {
    "wej": 124,
    "wyj": 175,
    "rozumowanie": 41
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T2_kajakarz",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Na ile różnych sposobów można ustawić wszystkie litery słowa KAJAKARZ tak, by żadne dwie litery A nie stały obok siebie? Ustawienia różniące się tylko zamianą identycznych liter traktujemy jako jedno. Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "**Litery słowa KAJAKARZ:** K, A, J, A, K, A, R, Z, czyli 8 liter. Wśród nich są A ×3, K ×2 oraz J, R, Z po razie.\n\n**Krok 1. Ustawiamy litery inne niż A.**\nSą to K, K, J, R, Z (5 liter, w tym dwie identyczne K). Liczba ustawień:\n5! / 2! = 120 / 2 = 60.\n\n**Krok 2. Wstawiamy litery A.**\nPięć liter wyznacza 6 miejsc: przed pierwszą, między kolejnymi i po ostatniej. Żeby żadne dwie litery A nie stały obok siebie, każda musi trafić w inne miejsce. Wybieramy więc 3 miejsca z 6:\nC(6,3) = 20.\n\n**Wynik:** 60 · 20 = 1200.\n\nODPOWIEDŹ: 1200",
   "czas_s": 5.0,
   "usage": {
    "prompt_tokens": 203,
    "completion_tokens": 445,
    "total_tokens": 648,
    "cost": 0.009712,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.009712,
     "upstream_inference_prompt_cost": 0.000812,
     "upstream_inference_completions_cost": 0.0089
    },
    "completion_tokens_details": {
     "reasoning_tokens": 119,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.009712,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.009712,
   "ocena": {
    "odp": "1200",
    "ok": true
   },
   "tokeny": {
    "wej": 203,
    "wyj": 445,
    "rozumowanie": 119
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T3_palindromy",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Ile jest pięciocyfrowych liczb pierwszych, które są palindromami (czytane od tyłu dają tę samą liczbę)? Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Pięciocyfrowy palindrom ma postać abcba, czyli 10001a + 1010b + 100c, gdzie a ≠ 0.\n\n**1. Ograniczenie pierwszej cyfry.** Ostatnia cyfra równa się pierwszej. Dla a ∈ {2, 4, 5, 6, 8} liczba jest parzysta albo podzielna przez 5, więc nie jest pierwsza. Zostają a ∈ {1, 3, 7, 9}.\n\n**2. Liczba kandydatów.** Dla każdej z 4 wartości a jest 10 · 10 = 100 palindromów, razem 400.\n\n**3. Odsiewanie.** Suma cyfr wynosi 2a + 2b + c, co pozwala odrzucić wielokrotności 3. Pozostałych kandydatów trzeba sprawdzić dzieleniem przez kolejne liczby pierwsze aż do √99999 ≈ 316. Tego etapu nie przeprowadziłem tutaj ręcznie dla wszystkich kandydatów.\n\n**4. Wynik.** Liczbę 93 podaję jako znany wynik z tabel palindromów pierwszych (ciąg OEIS A016115). Według tych tabel liczby palindromów pierwszych o danej liczbie cyfr wynoszą:\n- 1 cyfra: 4,\n- 3 cyfry: 15,\n- 5 cyfr: 93,\n- 7 cyfr: 668.\n\nODPOWIEDŹ: 93",
   "czas_s": 6.87,
   "usage": {
    "prompt_tokens": 162,
    "completion_tokens": 603,
    "total_tokens": 765,
    "cost": 0.012708,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.012708,
     "upstream_inference_prompt_cost": 0.000648,
     "upstream_inference_completions_cost": 0.01206
    },
    "completion_tokens_details": {
     "reasoning_tokens": 77,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.012708,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.012708,
   "ocena": {
    "odp": "93",
    "ok": true
   },
   "tokeny": {
    "wej": 162,
    "wyj": 603,
    "rozumowanie": 77
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T3_palindromy",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Ile jest pięciocyfrowych liczb pierwszych, które są palindromami (czytane od tyłu dają tę samą liczbę)? Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "ODPOWIEDŹ: 93",
   "czas_s": 10.01,
   "usage": {
    "prompt_tokens": 105,
    "completion_tokens": 204,
    "total_tokens": 309,
    "cost": 0.00225,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.00225,
     "upstream_inference_prompt_cost": 0.00021,
     "upstream_inference_completions_cost": 0.00204
    },
    "completion_tokens_details": {
     "reasoning_tokens": 190,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.00225,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.00225,
   "ocena": {
    "odp": "93",
    "ok": true
   },
   "tokeny": {
    "wej": 105,
    "wyj": 204,
    "rozumowanie": 190
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T4_monty",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Teleturniej: za jednymi z trzech drzwi jest samochód, za dwoma kozy. Wybierasz drzwi nr 1. Prowadzący NIE WIE, gdzie jest samochód: otwiera losowo jedne z dwóch pozostałych drzwi i przypadkiem jest tam koza. Jakie jest prawdopodobieństwo wygrania samochodu, jeśli teraz zmienisz wybór? Podaj dokładny ułamek.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "**Zdarzenia (wybrano drzwi nr 1):**\n\n- **Samochód za drzwiami 1** (prawdopodobieństwo 1/3). Prowadzący na pewno odsłoni kozę. Łącznie: 1/3 · 1 = **1/3**.\n- **Samochód za drzwiami 2 lub 3** (prawdopodobieństwo 2/3). Prowadzący wybiera losowo, więc kozę odsłoni z prawdopodobieństwem 1/2. Łącznie: 2/3 · 1/2 = **1/3**.\n\n**Warunkowanie na to, że pokazano kozę:**\n\nPrawdopodobieństwo, że prowadzący odsłonił kozę, wynosi 1/3 + 1/3 = 2/3.\n\nP(wygrana po zmianie | koza) = (1/3) / (2/3) = 1/2.\n\n**Dlaczego wynik różni się od klasycznego 2/3:** prowadzący nie wie, gdzie jest samochód. Przypadkowe odsłonięcie kozy daje więc mniej informacji niż w klasycznej wersji, w której prowadzący celowo omija samochód. Obie strategie, zmiana i pozostanie, są tu równie dobre.\n\nODPOWIEDŹ: 1/2",
   "czas_s": 6.44,
   "usage": {
    "prompt_tokens": 268,
    "completion_tokens": 557,
    "total_tokens": 825,
    "cost": 0.012212,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.012212,
     "upstream_inference_prompt_cost": 0.001072,
     "upstream_inference_completions_cost": 0.01114
    },
    "completion_tokens_details": {
     "reasoning_tokens": 108,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.012212,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.012212,
   "ocena": {
    "odp": "1/2",
    "ok": true
   },
   "tokeny": {
    "wej": 268,
    "wyj": 557,
    "rozumowanie": 108
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T1_dni_robocze",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Ile dni roboczych (poniedziałek-piątek, z wyłączeniem dni ustawowo wolnych od pracy w Polsce według stanu prawnego na listopad 2026) jest w roku kalendarzowym 2027? Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Aby obliczyć liczbę dni roboczych w 2027 roku (zdefiniowanych jako dni od poniedziałku do piątku z wyłączeniem świąt ustawowo wolnych od pracy):\n\n1. **Liczba wszystkich dni w 2027 roku:**\n   Rok 2027 nie jest rokiem przestępnym i ma **365 dni** (rozpoczyna się w piątek 1 stycznia i kończy w piątek 31 grudnia).\n   - Liczba pełnych tygodni: 52 (co daje $52 \\times 5 = 260$ dni od poniedziałku do piątku oraz 104 dni weekendowych).\n   - Dodatkowy 365. dzień roku to piątek.\n   - Łączna liczba dni od poniedziałku do piątku wynosi: $260 + 1 = \\mathbf{261}$.\n\n2. **Dni ustawowo wolne od pracy w 2027 roku przypadające od poniedziałku do piątku** (z uwzględnieniem wolnej Wigilii obowiązującej od 2025 roku):\n   - **1 stycznia** (Nowy Rok) – piątek\n   - **6 stycznia** (Święto Trzech Króli) – środa\n   - **29 marca** (Poniedziałek Wielkanocny) – poniedziałek\n   - **3 maja** (Święto Narodowe Trzeciego Maja) – poniedziałek\n   - **27 maja** (Boże Ciało) – czwartek\n   - **1 listopada** (Wszystkich Świętych) – poniedziałek\n   - **11 listopada** (Narodowe Święto Niepodległości) – czwartek\n   - **24 grudnia** (Wigilia Bożego Narodzenia) – piątek\n\n   Pozostałe święta przypadają w weekendy: Wielkanoc (28 marca – niedziela), Święto Pracy (1 maja – sobota), Zielone Świątki (16 maja – niedziela), Wniebowzięcie NMP (15 sierpnia – niedziela), pierwszy dzień Bożego Narodzenia (25 grudnia – sobota) oraz drugi dzień Bożego Narodzenia (26 grudnia – niedziela).\n\n3. **Obliczenie dni roboczych:**\n   - Odliczając 8 świąt przypadających w dni powszednie (poniedziałek–piątek): $261 - 8 = \\mathbf{253}$ dni.\n   *(Uwaga: w rozumieniu wymiaru czasu pracy z Kodeksu pracy, za 2 święta przypadające w sobotę – 1 maja i 25 grudnia – pracodawca wyznacza dodatkowe 2 dni wolne, co daje 251 dni faktycznego wymiaru pracy dla pełnego etatu).*\n\nZgodnie z definicją podaną w pytaniu (dni poniedziałek–piątek z wyłączeniem dni ustawowo wolnych od pracy):\n\nODPOWIEDŹ: 253",
   "czas_s": 34.22,
   "usage": {
    "prompt_tokens": 134,
    "completion_tokens": 2152,
    "total_tokens": 2286,
    "cost": 0.021788,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.021788,
     "upstream_inference_prompt_cost": 0.000268,
     "upstream_inference_completions_cost": 0.02152
    },
    "completion_tokens_details": {
     "reasoning_tokens": 1482,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.021788,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.021788,
   "ocena": {
    "odp": "253",
    "ok": true
   },
   "tokeny": {
    "wej": 134,
    "wyj": 2152,
    "rozumowanie": 1482
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T4_monty",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Teleturniej: za jednymi z trzech drzwi jest samochód, za dwoma kozy. Wybierasz drzwi nr 1. Prowadzący NIE WIE, gdzie jest samochód: otwiera losowo jedne z dwóch pozostałych drzwi i przypadkiem jest tam koza. Jakie jest prawdopodobieństwo wygrania samochodu, jeśli teraz zmienisz wybór? Podaj dokładny ułamek.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Jeśli samochód jest za drzwiami nr 1 (prawdopodobieństwo \\(1/3\\)), prowadzący na pewno odsłoni kozę.\n\nJeśli samochód jest za innymi drzwiami (prawdopodobieństwo \\(2/3\\)), prowadzący odsłoni kozę z prawdopodobieństwem \\(1/2\\). Wtedy zmiana oznacza wygraną.\n\nPo zaobserwowaniu kozy prawdopodobieństwo wygranej przy zmianie wynosi więc:\n\\[\n\\frac{(2/3)(1/2)}{(1/3)\\cdot 1+(2/3)(1/2)}=\\frac12.\n\\]\n\nODPOWIEDŹ: 1/2",
   "czas_s": 6.47,
   "usage": {
    "prompt_tokens": 162,
    "completion_tokens": 218,
    "total_tokens": 380,
    "cost": 0.002504,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.002504,
     "upstream_inference_prompt_cost": 0.000324,
     "upstream_inference_completions_cost": 0.00218
    },
    "completion_tokens_details": {
     "reasoning_tokens": 49,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.002504,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.002504,
   "ocena": {
    "odp": "1/2",
    "ok": true
   },
   "tokeny": {
    "wej": 162,
    "wyj": 218,
    "rozumowanie": 49
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T5_tysiac_dni",
   "model": "anthropic/claude-opus-5.5",
   "provider": "Claude Platform on AWS",
   "prompt": "Dzień 9 listopada 2026 to dzień numer 1. Jaka data przypada na dzień numer 1000? Podaj datę w formacie RRRR-MM-DD.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "**Rozumowanie:** Dzień numer 1000 to 9 listopada 2026 + 999 dni.\n\n- Od 2026-11-09 do 2027-11-09 mija 365 dni (rok 2027 nie jest przestępny).\n- Od 2027-11-09 do 2028-11-09 mija 366 dni (obejmuje 29 lutego 2028).\n- Razem 731 dni, więc zostaje 999 − 731 = 268 dni.\n\nOd 2028-11-09 odliczam pozostałe 268 dni:\n\n| Odcinek | Dni | Pozostało |\n|---|---|---|\n| do 30 listopada | 21 | 247 |\n| grudzień | 31 | 216 |\n| styczeń 2029 | 31 | 185 |\n| luty | 28 | 157 |\n| marzec | 31 | 126 |\n| kwiecień | 30 | 96 |\n| maj | 31 | 65 |\n| czerwiec | 30 | 35 |\n| lipiec | 31 | 4 |\n\nPozostałe 4 dni dają 4 sierpnia 2029.\n\nODPOWIEDŹ: 2029-08-04",
   "czas_s": 6.52,
   "usage": {
    "prompt_tokens": 165,
    "completion_tokens": 703,
    "total_tokens": 868,
    "cost": 0.01472,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.01472,
     "upstream_inference_prompt_cost": 0.00066,
     "upstream_inference_completions_cost": 0.01406
    },
    "completion_tokens_details": {
     "reasoning_tokens": 307,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.01472,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.01472,
   "ocena": {
    "odp": "2029-08-04",
    "ok": true
   },
   "tokeny": {
    "wej": 165,
    "wyj": 703,
    "rozumowanie": 307
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T2_kajakarz",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Na ile różnych sposobów można ustawić wszystkie litery słowa KAJAKARZ tak, by żadne dwie litery A nie stały obok siebie? Ustawienia różniące się tylko zamianą identycznych liter traktujemy jako jedno. Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Słowo **KAJAKARZ** składa się z 8 liter, w tym:\n* 3 liter A,\n* 2 liter K,\n* po 1 literze J, R, Z.\n\nAby żadne dwie litery A nie stały obok siebie, najpierw ustawiamy pozostałe 5 liter (K, K, J, R, Z). Ponieważ litera K powtarza się dwukrotnie, liczba takich ustawień wynosi:\n$$\\frac{5!}{2!} = \\frac{120}{2} = 60$$\n\nUstawione 5 liter tworzy 6 miejsc (pięć luk między nimi oraz dwa miejsca na krańcach), w które możemy wstawić litery A:\n`_ X _ X _ X _ X _ X _`\n\nAby żadne dwie litery A ze sobą nie sąsiadowały, musimy wybrać 3 różne miejsca z 6 dostępnych. Ponieważ litery A są nierozróżnialne, liczba sposobów ich rozmieszczenia to:\n$$\\binom{6}{3} = \\frac{6 \\cdot 5 \\cdot 4}{3 \\cdot 2 \\cdot 1} = 20$$\n\nMnożąc obie wartości, otrzymujemy całkowitą liczbę szukanych ustawień:\n$$60 \\cdot 20 = 1200$$\n\nODPOWIEDŹ: 1200",
   "czas_s": 8.16,
   "usage": {
    "prompt_tokens": 133,
    "completion_tokens": 531,
    "total_tokens": 664,
    "cost": 0.005576,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.005576,
     "upstream_inference_prompt_cost": 0.000266,
     "upstream_inference_completions_cost": 0.00531
    },
    "completion_tokens_details": {
     "reasoning_tokens": 224,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.005576,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.005576,
   "ocena": {
    "odp": "1200",
    "ok": true
   },
   "tokeny": {
    "wej": 133,
    "wyj": 531,
    "rozumowanie": 224
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T5_tysiac_dni",
   "model": "openai/gpt-6.1-sol",
   "provider": "OpenAI",
   "prompt": "Dzień 9 listopada 2026 to dzień numer 1. Jaka data przypada na dzień numer 1000? Podaj datę w formacie RRRR-MM-DD.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Dzień numer 1000 przypada **999 dni po 9 listopada 2026**. Do 9 listopada 2028 mijają 731 dni (rok 2028 jest przestępny). Pozostałe 268 dni prowadzi do 4 sierpnia 2029.\n\nODPOWIEDŹ: 2029-08-04",
   "czas_s": 7.34,
   "usage": {
    "prompt_tokens": 104,
    "completion_tokens": 286,
    "total_tokens": 390,
    "cost": 0.003068,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.003068,
     "upstream_inference_prompt_cost": 0.000208,
     "upstream_inference_completions_cost": 0.00286
    },
    "completion_tokens_details": {
     "reasoning_tokens": 203,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.003068,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.003068,
   "ocena": {
    "odp": "2029-08-04",
    "ok": true
   },
   "tokeny": {
    "wej": 104,
    "wyj": 286,
    "rozumowanie": 203
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T3_palindromy",
   "model": "google/gemini-4-argon",
   "provider": "Google AI Studio",
   "prompt": "Ile jest pięciocyfrowych liczb pierwszych, które są palindromami (czytane od tyłu dają tę samą liczbę)? Podaj liczbę.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Aby pięciocyfrowa liczba postaci $abcba$ była liczbą pierwszą i jednocześnie palindromem, jej pierwsza (i zarazem ostatnia) cyfra $a$ nie może być parzysta ani równa $5$. Oznacza to, że $a \\in \\{1, 3, 7, 9\\}$. \n\nPozostałe cyfry $b$ oraz $c$ mogą przyjmować dowolne wartości od $0$ do $9$. Daje to łącznie $4 \\times 10 \\times 10 = 400$ kandydatów na takie liczby pierwsze. \n\nPo odrzuceniu liczb złożonych (w tym w pierwszej kolejności podzielnych przez 3, gdzie suma cyfr $2a + 2b + c$ jest wielokrotnością 3, a następnie podzielnych przez kolejne liczby pierwsze aż do $\\sqrt{99999} \\approx 316$), otrzymujemy dokładnie 93 takie liczby (zgodnie z ciągiem OEIS A016115 określającym liczbę palindromicznych liczb pierwszych o zadanej liczbie cyfr: 4, 1, 15, 0, 93...).\n\nODPOWIEDŹ: 93",
   "czas_s": 23.22,
   "usage": {
    "prompt_tokens": 110,
    "completion_tokens": 741,
    "total_tokens": 851,
    "cost": 0.00763,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.00763,
     "upstream_inference_prompt_cost": 0.00022,
     "upstream_inference_completions_cost": 0.00741
    },
    "completion_tokens_details": {
     "reasoning_tokens": 470,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.00763,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.00763,
   "ocena": {
    "odp": "93",
    "ok": true
   },
   "tokeny": {
    "wej": 110,
    "wyj": 741,
    "rozumowanie": 470
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T4_monty",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Teleturniej: za jednymi z trzech drzwi jest samochód, za dwoma kozy. Wybierasz drzwi nr 1. Prowadzący NIE WIE, gdzie jest samochód: otwiera losowo jedne z dwóch pozostałych drzwi i przypadkiem jest tam koza. Jakie jest prawdopodobieństwo wygrania samochodu, jeśli teraz zmienisz wybór? Podaj dokładny ułamek.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "To jest wariant problemu Monty'ego Halla znany jako \"Ignorant Monty\" (Nieświadomy Prowadzący). Ponieważ prowadzący nie wie, gdzie jest samochód, fakt, że otworzył drzwi z kozą, jest dziełem przypadku i dostarcza nam zupełnie innych informacji niż w klasycznym problemie.\n\nPrzeanalizujmy wszystkie możliwe scenariusze (zakładając, że wybierasz drzwi nr 1, a prowadzący losowo otwiera drzwi nr 2 lub 3). Początkowo samochód może być za drzwiami 1, 2 lub 3 z prawdopodobieństwem 1/3 dla każdego przypadku:\n\n1. **Samochód jest za drzwiami 1 (prawdopodobieństwo 1/3):**\n   * Prowadzący otwiera drzwi 2 (koza) – szansa 1/6.\n   * Prowadzący otwiera drzwi 3 (koza) – szansa 1/6.\n2. **Samochód jest za drzwiami 2 (prawdopodobieństwo 1/3):**\n   * Prowadzący otwiera drzwi 2 (samochód) – szansa 1/6. *(Ten przypadek odrzucamy, bo w zadaniu prowadzący odsłonił kozę).*\n   * Prowadzący otwiera drzwi 3 (koza) – szansa 1/6.\n3. **Samochód jest za drzwiami 3 (prawdopodobieństwo 1/3):**\n   * Prowadzący otwiera drzwi 2 (koza) – szansa 1/6.\n   * Prowadzący otwiera drzwi 3 (samochód) – szansa 1/6. *(Ten przypadek również odrzucamy).*\n\nZ 6 równie prawdopodobnych zdarzeń elementarnych (każde o szansie 1/6), warunkom zadania (prowadzący odsłania kozę) sprzyjają tylko 4 zdarzenia:\n* Dwa z nich występują, gdy samochód jest za drzwiami nr 1 (zmiana wyboru daje przegraną).\n* Dwa z nich występują, gdy samochód jest za pozostałymi zamkniętymi drzwiami (zmiana wyboru daje wygraną).\n\nZatem pod warunkiem, że zobaczyliśmy kozę, szansa na to, że samochód jest za drugimi zamkniętymi drzwiami, wynosi 2 z 4, czyli 1/2.\n\nODPOWIEDŹ: 1/2",
   "czas_s": 9.52,
   "usage": {
    "prompt_tokens": 159,
    "completion_tokens": 547,
    "total_tokens": 706,
    "cost": 0.005788,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.005788,
     "upstream_inference_prompt_cost": 0.000318,
     "upstream_inference_completions_cost": 0.00547
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.005788,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.005788,
   "ocena": {
    "odp": "1/2",
    "ok": true
   },
   "tokeny": {
    "wej": 159,
    "wyj": 547,
    "rozumowanie": 0
   }
  },
  {
   "kategoria": "trudne",
   "zadanie": "T5_tysiac_dni",
   "model": "google/gemini-4-argon",
   "provider": "Google",
   "prompt": "Dzień 9 listopada 2026 to dzień numer 1. Jaka data przypada na dzień numer 1000? Podaj datę w formacie RRRR-MM-DD.\n\nNie masz dostępu do narzędzi ani do uruchamiania kodu. Możesz krótko pokazać tok rozumowania. Ostatnia linia odpowiedzi musi mieć dokładnie format: ODPOWIEDŹ: <odpowiedź>",
   "odpowiedz": "Skoro 9 listopada 2026 to dzień 1, do tej daty musimy dodać 999 dni, aby otrzymać dzień 1000.\n\n1. Pozostałe dni w 2026 roku:\n   - Listopad ma 30 dni, więc zostało 30 - 9 = 21 dni.\n   - Grudzień ma 31 dni.\n   - Razem do końca 2026 roku są 52 dni (31 grudnia 2026 to dzień 53).\n   - Pozostaje do dodania: 999 - 52 = 947 dni.\n\n2. Kolejne pełne lata:\n   - Rok 2027 (zwykły): 365 dni. Pozostaje: 947 - 365 = 582 dni.\n   - Rok 2028 (przestępny): 366 dni. Pozostaje: 582 - 366 = 216 dni.\n\n3. Dni w roku 2029 (zwykły):\n   - Styczeń: 31 (pozostaje 216 - 31 = 185)\n   - Luty: 28 (pozostaje 185 - 28 = 157)\n   - Marzec: 31 (pozostaje 157 - 31 = 126)\n   - Kwiecień: 30 (pozostaje 126 - 30 = 96)\n   - Maj: 31 (pozostaje 96 - 31 = 65)\n   - Czerwiec: 30 (pozostaje 65 - 30 = 35)\n   - Lipiec: 31 (pozostaje 35 - 31 = 4)\n\n4. Ostatnie 4 dni przypadają na sierpień 2029 roku, czyli będzie to 4 sierpnia 2029.\n\nODPOWIEDŹ: 2029-08-04",
   "czas_s": 8.36,
   "usage": {
    "prompt_tokens": 122,
    "completion_tokens": 496,
    "total_tokens": 618,
    "cost": 0.005204,
    "is_byok": false,
    "prompt_tokens_details": {
     "cached_tokens": 0,
     "cache_write_tokens": 0,
     "audio_tokens": 0,
     "video_tokens": 0
    },
    "cost_details": {
     "upstream_inference_cost": 0.005204,
     "upstream_inference_prompt_cost": 0.000244,
     "upstream_inference_completions_cost": 0.00496
    },
    "completion_tokens_details": {
     "reasoning_tokens": 0,
     "image_tokens": 0,
     "audio_tokens": 0
    }
   },
   "koszt_usd": 0.005204,
   "finish": "stop",
   "blad": null,
   "koszt_wg_cennika_usd": 0.005204,
   "ocena": {
    "odp": "2029-08-04",
    "ok": true
   },
   "tokeny": {
    "wej": 122,
    "wyj": 496,
    "rozumowanie": 0
   }
  }
 ],
 "sedzia": {
  "model": "openai/gpt-6.1-sol",
  "metoda": "2 ślepe rundy z losową kolejnością A-D na zadanie",
  "oceny": {
   "google/gemini-4-argon": {
    "P1_urzad": [
     {
      "bledy": 2,
      "naturalnosc": 7,
      "wykonanie": 9,
      "pozycja_w_rankingu": 3,
      "lista_bledow": [
       {
        "cytat": "Zgodnie z obowiązującymi przepisami, przy rozliczeniu elektronicznym",
        "poprawka": "Zgodnie z obowiązującymi przepisami przy rozliczeniu elektronicznym"
       },
       {
        "cytat": "Z poważaniem,",
        "poprawka": "Z poważaniem"
       }
      ],
      "uwagi": "Tekst mieści się w limicie i zawiera wymagane elementy, ale jest nadmiernie szablonowy oraz dodaje niepotwierdzoną w poleceniu informację o poprawnym przyjęciu dokumentu i posiadaniu UPO."
     },
     {
      "bledy": 4,
      "naturalnosc": 6,
      "wykonanie": 6,
      "pozycja_w_rankingu": 4,
      "lista_bledow": [
       {
        "cytat": "zwrotu nadpłaty podatku PIT-37",
        "poprawka": "zwrotu nadpłaty wynikającej z zeznania PIT-37"
       },
       {
        "cytat": "Urzędowe Poświadczenie Odbioru",
        "poprawka": "urzędowe poświadczenie odbioru"
       },
       {
        "cytat": "Zgodnie z obowiązującymi przepisami, przy rozliczeniu elektronicznym",
        "poprawka": "Zgodnie z obowiązującymi przepisami przy rozliczeniu elektronicznym"
       },
       {
        "cytat": "Z poważaniem,",
        "poprawka": "Z poważaniem"
       }
      ],
      "uwagi": "Tekst przekracza limit 200 słów, jest rozwlekły i szablonowy, a ponadto dodaje niepodane w poleceniu informacje o potwierdzeniu przyjęcia zeznania oraz wcześniejszym zgłoszeniu rachunku."
     }
    ],
    "P2_streszczenie": [
     {
      "bledy": 1,
      "naturalnosc": 8,
      "wykonanie": 6,
      "pozycja_w_rankingu": 4,
      "lista_bledow": [
       "„godzina jazdy wyniesie 3 lub 5 zł” → „godzina jazdy będzie kosztować 3 lub 5 zł”"
      ],
      "uwagi": "Tekst liczy 80 słów, przekracza więc limit o 10, a ponadto nie przyporządkowuje stawek rodzajom rowerów i pomija naliczanie opłat za każdą rozpoczętą godzinę."
     },
     {
      "bledy": 1,
      "naturalnosc": 8,
      "wykonanie": 5,
      "pozycja_w_rankingu": 4,
      "lista_bledow": [
       {
        "cytat": "godzina jazdy wyniesie 3 lub 5 zł",
        "poprawka": "godzina jazdy będzie kosztować 3 lub 5 zł"
       }
      ],
      "uwagi": "Tekst liczy 80 słów, przekracza więc limit, a ponadto pomija naliczanie opłat za rozpoczętą godzinę i nie przypisuje jednoznacznie wyższej stawki rowerom elektrycznym."
     }
    ],
    "P3_marketing": [
     {
      "bledy": 0,
      "naturalnosc": 7,
      "wykonanie": 7,
      "pozycja_w_rankingu": 4,
      "lista_bledow": [],
      "uwagi": "Tekst mieści się w limicie i zawiera wymagane informacje, ale reklamowe formuły osłabiają naturalność, a palenie poprzedniego dnia, brak umów oraz anulowanie jednym kliknięciem to obietnice niepotwierdzone w poleceniu."
     },
     {
      "bledy": 0,
      "naturalnosc": 7,
      "wykonanie": 7,
      "pozycja_w_rankingu": 4,
      "lista_bledow": [],
      "uwagi": "Długość i podstawowe elementy są zgodne z poleceniem, jednak deklaracja palenia poprzedniego dnia, anulowanie jednym kliknięciem i brak umów to dopisane obietnice, a reklamowe sformułowania oraz ostentacyjne odcinanie się od frazesów osłabiają naturalność."
     }
    ]
   },
   "anthropic/claude-opus-5.5": {
    "P1_urzad": [
     {
      "bledy": 0,
      "naturalnosc": 9,
      "wykonanie": 8,
      "pozycja_w_rankingu": 2,
      "lista_bledow": [],
      "uwagi": "Tekst jest zwięzły i językowo poprawny, lecz błędnie wskazuje 31 marca jako koniec terminu: zeznanie złożone przed 15 lutego uznaje się za złożone 15 lutego, więc termin upływa 1 kwietnia 2026 r."
     },
     {
      "bledy": 0,
      "naturalnosc": 9,
      "wykonanie": 8,
      "pozycja_w_rankingu": 2,
      "lista_bledow": [],
      "uwagi": "Tekst jest językowo poprawny i kompletny, ale błędnie wskazuje 31 marca jako koniec terminu: zeznanie złożone przed 15 lutego uznaje się za złożone 15 lutego, więc termin upływa 1 kwietnia 2026 r."
     }
    ],
    "P2_streszczenie": [
     {
      "bledy": 0,
      "naturalnosc": 9,
      "wykonanie": 10,
      "pozycja_w_rankingu": 1,
      "lista_bledow": [],
      "uwagi": "Tekst liczy 68 słów, zachowuje najważniejsze liczby i zasady naliczania opłat oraz wiernie streszcza źródło w jednym akapicie."
     },
     {
      "bledy": 0,
      "naturalnosc": 9,
      "wykonanie": 10,
      "pozycja_w_rankingu": 1,
      "lista_bledow": [],
      "uwagi": "Tekst liczy 68 słów, zachowuje kluczowe liczby i zasady naliczania opłat oraz wiernie przedstawia wybrane informacje."
     }
    ],
    "P3_marketing": [
     {
      "bledy": 0,
      "naturalnosc": 9,
      "wykonanie": 6,
      "pozycja_w_rankingu": 2,
      "lista_bledow": [],
      "uwagi": "Tekst jest naturalny i ma właściwą długość, lecz pomija nazwę Ziarnko oraz dopisuje niepotwierdzone szczegóły dotyczące terminu i częstotliwości palenia, oznakowania paczek, obsługi subskrypcji i indywidualnego dobierania kawy."
     },
     {
      "bledy": 0,
      "naturalnosc": 9,
      "wykonanie": 7,
      "pozycja_w_rankingu": 3,
      "lista_bledow": [],
      "uwagi": "Spokojny, naturalny tekst zachowuje limit i podaje warunki subskrypcji, ale nie wymienia nazwy Ziarnko oraz bez podstaw obiecuje konkretny termin i częstotliwość palenia, dodatkowe materiały oraz indywidualne dobieranie kolejnej kawy."
     }
    ]
   },
   "anthropic/claude-fable-5.2": {
    "P1_urzad": [
     {
      "bledy": 0,
      "naturalnosc": 8,
      "wykonanie": 8,
      "pozycja_w_rankingu": 4,
      "lista_bledow": [],
      "uwagi": "Tekst spełnia wymagania formalne i językowe, ale niepotrzebnie przywołuje błędną podstawę prawną: termin 45 dni określa art. 77 § 1 pkt 5a Ordynacji podatkowej, a nie pkt 5."
     },
     {
      "bledy": 0,
      "naturalnosc": 8,
      "wykonanie": 8,
      "pozycja_w_rankingu": 3,
      "lista_bledow": [],
      "uwagi": "Tekst spełnia wymagania formalne, lecz zawiera błędne odesłanie prawne: termin 45 dni określa art. 77 § 1 pkt 5a, a nie pkt 5 Ordynacji podatkowej."
     }
    ],
    "P2_streszczenie": [
     {
      "bledy": 1,
      "naturalnosc": 8,
      "wykonanie": 7,
      "pozycja_w_rankingu": 3,
      "lista_bledow": [
       "„wskazując na brak dróg rowerowych (11 km” → „wskazując na niedobór dróg rowerowych (11 km”"
      ],
      "uwagi": "Tekst liczy 71 słów, przekracza limit o jedno słowo, nieprecyzyjnie mówi o braku istniejących dróg i pomija naliczanie opłat za rozpoczętą godzinę."
     },
     {
      "bledy": 0,
      "naturalnosc": 8,
      "wykonanie": 6,
      "pozycja_w_rankingu": 3,
      "lista_bledow": [],
      "uwagi": "Tekst liczy 71 słów, przekracza więc limit, a dodatkowo zastępuje niedobór wydzielonych dróg ich brakiem i pomija naliczanie opłat za rozpoczętą godzinę."
     }
    ],
    "P3_marketing": [
     {
      "bledy": 0,
      "naturalnosc": 8,
      "wykonanie": 6,
      "pozycja_w_rankingu": 3,
      "lista_bledow": [],
      "uwagi": "Tekst mieści się w limicie i brzmi swobodnie, ale pomija nazwę Ziarnko oraz bez podstaw deklaruje między innymi wysyłkę zaraz po wypaleniu, brak umowy, datę palenia na paczce i brak infolinii."
     },
     {
      "bledy": 0,
      "naturalnosc": 9,
      "wykonanie": 8,
      "pozycja_w_rankingu": 2,
      "lista_bledow": [],
      "uwagi": "Tekst mieści się w limicie, brzmi swobodnie i zawiera wymagane warunki oferty, ale pomija nazwę Ziarnko oraz dopisuje niepotwierdzone informacje o braku umowy i infolinii, terminie wysyłki oraz oznaczaniu daty palenia."
     }
    ]
   },
   "openai/gpt-6.1-sol": {
    "P1_urzad": [
     {
      "bledy": 0,
      "naturalnosc": 9,
      "wykonanie": 10,
      "pozycja_w_rankingu": 1,
      "lista_bledow": [],
      "uwagi": "Najlepiej realizuje polecenie: jest poprawny, kompletny, mieści się w limicie i zachowuje współczesny ton urzędowy bez ryzykownych dopowiedzeń prawnych."
     },
     {
      "bledy": 0,
      "naturalnosc": 9,
      "wykonanie": 10,
      "pozycja_w_rankingu": 1,
      "lista_bledow": [],
      "uwagi": "Poprawny, rzeczowy e-mail spełniający wymagania dotyczące treści, długości i tonu."
     }
    ],
    "P2_streszczenie": [
     {
      "bledy": 0,
      "naturalnosc": 8,
      "wykonanie": 9,
      "pozycja_w_rankingu": 2,
      "lista_bledow": [],
      "uwagi": "Tekst liczy 65 słów i zachowuje wiele istotnych danych, choć telegraficzny zapis opłat osłabia płynność, a zamiast „kolejna” precyzyjniej byłoby napisać „każda kolejna”."
     },
     {
      "bledy": 0,
      "naturalnosc": 8,
      "wykonanie": 9,
      "pozycja_w_rankingu": 2,
      "lista_bledow": [],
      "uwagi": "Tekst liczy 65 słów i zachowuje wiele istotnych liczb, lecz sformułowanie o kolejnej rozpoczętej godzinie jest mniej precyzyjne niż informacja o każdej kolejnej rozpoczętej godzinie."
     }
    ],
    "P3_marketing": [
     {
      "bledy": 1,
      "naturalnosc": 9,
      "wykonanie": 10,
      "pozycja_w_rankingu": 1,
      "lista_bledow": [
       {
        "cytat": "na ostatnim kroku zamówienia",
        "poprawka": "na ostatnim etapie składania zamówienia"
       }
      ],
      "uwagi": "Najwierniej realizuje polecenie: zachowuje limit, podaje wszystkie wymagane informacje i kończy się jednym wezwaniem do działania, choć końcowy akapit nieco rozwadnia przekaz."
     },
     {
      "bledy": 0,
      "naturalnosc": 8,
      "wykonanie": 10,
      "pozycja_w_rankingu": 1,
      "lista_bledow": [],
      "uwagi": "Najwierniej realizuje polecenie: zachowuje limit, przedstawia markę i wszystkie warunki oferty bez dopisywania istotnych obietnic, choć zakończenie jest nieco rozwlekłe i bardziej szablonowe niż reszta."
     }
    ]
   }
  },
  "koszt_usd": 0.113546
 },
 "podsumowanie": {
  "anthropic/claude-fable-5.2": {
   "kod": [
    76,
    76
   ],
   "kod_trudny": [
    60,
    60
   ],
   "rozum": [
    8,
    8
   ],
   "rozum_trudne": [
    5,
    5
   ],
   "wizja": [
    10,
    10
   ],
   "wizja_czysty_json": 2,
   "ttft_med": 2.2385,
   "tok_s_esej_kod": 119.15,
   "czas_esej": 19.1435,
   "slowa_s_esej": 26.15,
   "sedzia_bledy_suma": 0.5,
   "sedzia_naturalnosc": 8.17,
   "sedzia_wykonanie": 7.17,
   "sedzia_sr_miejsce": 3,
   "koszt": {
    "kod": 0.292832,
    "rozum": 0.093408,
    "polski": 0.060248,
    "wizja": 0.044472,
    "szybkosc": 0.405472,
    "trudne": 0.261904
   },
   "koszt_suma": 1.1583,
   "koszt_bez_szybkosci": 0.7529,
   "tokeny_wyj": 26230
  },
  "anthropic/claude-opus-5.5": {
   "kod": [
    76,
    76
   ],
   "kod_trudny": [
    60,
    60
   ],
   "rozum": [
    8,
    8
   ],
   "rozum_trudne": [
    5,
    5
   ],
   "wizja": [
    10,
    10
   ],
   "wizja_czysty_json": 3,
   "ttft_med": 2.6505,
   "tok_s_esej_kod": 119.5,
   "czas_esej": 21.774,
   "slowa_s_esej": 21.4,
   "sedzia_bledy_suma": 0.0,
   "sedzia_naturalnosc": 9,
   "sedzia_wykonanie": 8.17,
   "sedzia_sr_miejsce": 1.83,
   "koszt": {
    "kod": 0.150796,
    "rozum": 0.083284,
    "polski": 0.107844,
    "wizja": 0.025636,
    "szybkosc": 0.209896,
    "trudne": 0.156972
   },
   "koszt_suma": 0.7344,
   "koszt_bez_szybkosci": 0.5245,
   "tokeny_wyj": 33993
  },
  "openai/gpt-6.1-sol": {
   "kod": [
    76,
    76
   ],
   "kod_trudny": [
    60,
    60
   ],
   "rozum": [
    8,
    8
   ],
   "rozum_trudne": [
    5,
    5
   ],
   "wizja": [
    10,
    10
   ],
   "wizja_czysty_json": 3,
   "ttft_med": 1.4849999999999999,
   "tok_s_esej_kod": 77.75,
   "czas_esej": 25.7265,
   "slowa_s_esej": 19.200000000000003,
   "sedzia_bledy_suma": 0.5,
   "sedzia_naturalnosc": 8.5,
   "sedzia_wykonanie": 9.67,
   "sedzia_sr_miejsce": 1.33,
   "koszt": {
    "kod": 0.04076,
    "rozum": 0.01514,
    "polski": 0.014228,
    "wizja": 0.009138,
    "szybkosc": 0.062346,
    "trudne": 0.035662
   },
   "koszt_suma": 0.1773,
   "koszt_bez_szybkosci": 0.1149,
   "tokeny_wyj": 15706
  },
  "google/gemini-4-argon": {
   "kod": [
    76,
    76
   ],
   "kod_trudny": [
    60,
    60
   ],
   "rozum": [
    8,
    8
   ],
   "rozum_trudne": [
    5,
    5
   ],
   "wizja": [
    10,
    10
   ],
   "wizja_czysty_json": 0,
   "ttft_med": 3.4355,
   "tok_s_esej_kod": 76.5,
   "czas_esej": 18.8005,
   "slowa_s_esej": 25.65,
   "sedzia_bledy_suma": 4.0,
   "sedzia_naturalnosc": 7.17,
   "sedzia_wykonanie": 6.67,
   "sedzia_sr_miejsce": 3.83,
   "koszt": {
    "kod": 0.029498,
    "rozum": 0.028374,
    "polski": 0.01041,
    "wizja": 0.00862,
    "szybkosc": 0.07037,
    "trudne": 0.066698
   },
   "koszt_suma": 0.214,
   "koszt_bez_szybkosci": 0.1436,
   "tokeny_wyj": 19286
  },
  "anthropic/claude-fable-5.1": {
   "ttft_med": 1.9329999999999998,
   "tok_s_esej_kod": 87.0,
   "czas_esej": 26.174999999999997,
   "slowa_s_esej": 18.6,
   "providerzy": [
    "Anthropic",
    "Azure",
    "Google"
   ],
   "koszt_suma": 0.5195
  }
 },
 "koszt_calkowity_usd": 2.9171
}