{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "# Задачи · Лекция 8\n\n15 заданий по темам лекции и финальная задача. В пустых ячейках пишите решение, сверяйтесь с `# Ожидаемый вывод`. Стандартная библиотека Python."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Содержание\n\n1. KeyError и try/except  ·  *Раздел B*\n2. ValueError и raise  ·  *Раздел B*\n3. TypeError  ·  *Раздел B*\n4. AttributeError  ·  *Раздел B*\n5. Иерархия: LookupError  ·  *Раздел B*\n6. Порядок except  ·  *Раздел B*\n7. Объект исключения: type.__name__ и args  ·  *Раздел B*\n8. Множественные except  ·  *Раздел C*\n9. Блок else  ·  *Раздел C*\n10. Блок finally  ·  *Раздел C*\n11. UnboundLocalError в finally  ·  *Раздел C*\n12. raise по бизнес-правилу  ·  *Раздел D*\n13. with: свой класс  ·  *Раздел E*\n14. @contextmanager  ·  *Раздел E*\n15. logging: настройка и уровни  ·  *Раздел F*\n\n**Финальная задача**. Платёжный процессор: все темы вместе."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 1. KeyError и try/except\n\n*Раздел B*\n\nЕсли ключа нет в словаре, обращение `d['key']` бросает `KeyError`. Ловим его через `try/except` и возвращаем дефолт."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "fee_config = {\"P2P\": 1000, \"UTIL\": 500}\n\ntry:\n    fee = fee_config[\"CRYPTO\"]\nexcept KeyError as e:\n    print(f\"Ключ не настроен: {e}\")\n    fee = 0\n\nprint(f\"Комиссия: {fee}\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 1\n\nДан словарь курсов `rates = {'USD': 12500.0, 'EUR': 13600.0}`. Напишите функцию `get_rate(code: str) -> float`. Если кода нет, поймайте `KeyError` и верните `0.0`. Проверьте на `'USD'` и `'RUB'`."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   12500.0\n#   0.0"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 2. ValueError и raise\n\n*Раздел B*\n\n`ValueError` бросают, когда тип значения правильный, но содержание недопустимое."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "def set_limit(limit: float) -> None:\n    if limit <= 0:\n        raise ValueError(f\"Лимит должен быть > 0: {limit}\")\n    print(f\"Лимит: {limit}\")\n\ntry:\n    set_limit(-100)\nexcept ValueError as e:\n    print(f\"Отклонено: {e}\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 2\n\nНапишите `validate_phone(phone: str)`. Если строка не из 12 цифр, бросайте `ValueError(\"Телефон: ровно 12 цифр\")`. Для `'8901234'` поймайте ошибку и выведите `f'Ошибка: {e}'`. Для `'998901234567'` функция должна напечатать `'OK'` без ошибки."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   Ошибка: Телефон: ровно 12 цифр\n#   OK"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 3. TypeError\n\n*Раздел B*\n\n`TypeError` возникает при операции с несовместимыми типами. Например, умножение строки на `float`."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "try:\n    result = \"100\" * 0.5\nexcept TypeError as e:\n    print(f\"[TypeError] {e}\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 3\n\nФункция `calculate(amount, rate)` возвращает `amount * rate`. Вызовите её с `amount='1000'` (строка вместо числа). Поймайте `TypeError`, выведите имя класса ошибки и сообщение в формате `'[TypeError] <message>'`."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   [TypeError] can't multiply sequence by non-int of type 'float'"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 4. AttributeError\n\n*Раздел B*\n\n`AttributeError` возникает при обращении к атрибуту или методу, которого у объекта нет."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "class Card:\n    def pay(self, amount):\n        print(f\"Pay {amount}\")\n\ncard = Card()\ntry:\n    card.refund(100)\nexcept AttributeError as e:\n    print(f\"AttributeError: {e}\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 4\n\nДан класс `Logger` с методом `log(msg)`. Создайте экземпляр и попробуйте вызвать `info('hello')`, такого метода нет. Поймайте `AttributeError`, выведите `f'Ошибка: {e}'`."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   Ошибка: 'Logger' object has no attribute 'info'"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 5. Иерархия: LookupError\n\n*Раздел B*\n\n`KeyError` и `IndexError` это потомки `LookupError`. Один блок `except LookupError` ловит оба."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "for err in [KeyError(\"k\"), IndexError(\"i\")]:\n    try:\n        raise err\n    except LookupError as e:\n        print(f\"[{type(e).__name__}] {e}\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 5\n\nНапишите `safe_get(container, key)`, которая возвращает `container[key]`. Если возникает `KeyError` или `IndexError`, ловите через общий родитель `LookupError` и возвращайте `None`. Проверьте на `safe_get({'a': 1}, 'b')` и `safe_get([1, 2], 99)`."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   None\n#   None"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 6. Порядок except\n\n*Раздел B*\n\nКонкретные `except` идут сверху, общие снизу. Иначе общий поймает всё первым."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "try:\n    raise KeyError(\"k\")\nexcept KeyError:\n    print(\"KeyError handled\")\nexcept Exception:\n    print(\"generic\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 6\n\nНапишите функцию `process(value)`. В `try`: если `value < 0`, бросьте `ValueError(\"отрицательное\")`. Поставьте `except ValueError` ПЕРЕД `except Exception`. Вызовите `process(-100)`. Должно напечатать `'value: отрицательное'`."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   value: отрицательное"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 7. Объект исключения: type.__name__ и args\n\n*Раздел B*\n\nУ объекта исключения есть полезные атрибуты: `type(e).__name__` (имя класса), `e.args` (аргументы конструктора)."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "try:\n    raise ConnectionRefusedError(\"PostgreSQL недоступен\")\nexcept Exception as e:\n    print(f\"Тип: {type(e).__name__}\")\n    print(f\"args: {e.args}\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 7\n\nФункция `divide(a, b)` делит `a / b`. Вызовите `divide(10, 0)`. Поймайте `ZeroDivisionError`, выведите имя класса в формате `'Класс: <name>'` и `e.args` в формате `'Args: <args>'`."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   Класс: ZeroDivisionError\n#   Args: ('division by zero',)"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 8. Множественные except\n\n*Раздел C*\n\nРазные ошибки требуют разной реакции. Каждый тип ловится своим `except`."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "def call(scenario):\n    if scenario == \"timeout\":\n        raise TimeoutError(\"сеть упала\")\n    if scenario == \"value\":\n        raise ValueError(\"плохое значение\")\n    return \"OK\"\n\nfor s in [\"timeout\", \"value\", \"ok\"]:\n    try:\n        result = call(s)\n        print(f\"[OK] {result}\")\n    except TimeoutError as e:\n        print(f\"[RETRY] {e}\")\n    except ValueError as e:\n        print(f\"[REJECT] {e}\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 8\n\nФункция `parse_amount(s)` возвращает `float(s)`. Напишите `safe_parse(s)`: возвращает `float(s)` при успехе, `None` при `ValueError` (не число) или `TypeError` (не строка, например `None`). Проверьте на `'1500'`, `'abc'`, `None`."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   1500.0\n#   None\n#   None"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 9. Блок else\n\n*Раздел C*\n\nБлок `else` выполняется только если в `try` не было исключения. Удобно для кода «при успехе»."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "def parse(s):\n    try:\n        value = int(s)\n    except ValueError:\n        print(f\"Не число: {s}\")\n        return None\n    else:\n        print(f\"Успех: {value}\")\n        return value\n\nparse(\"42\")\nparse(\"abc\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 9\n\nНапишите `to_int(s)`. В `try`: `value = int(s)`. В `except ValueError`: напечатать `'Не число'`, вернуть `-1`. В `else`: напечатать `f'OK: {value}'`, вернуть `value`. Вызовите для `'100'` и `'xyz'`."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   OK: 100\n#   Не число"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 10. Блок finally\n\n*Раздел C*\n\nБлок `finally` выполняется всегда: при успехе, при пойманном исключении, даже при `return` из `try`."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "def process(x):\n    print(f\"START {x}\")\n    try:\n        if x < 0:\n            raise ValueError(\"отрицательное\")\n        return x * 2\n    except ValueError as e:\n        print(f\"ERROR {e}\")\n        return -1\n    finally:\n        print(f\"CLEANUP {x}\")\n\nprint(process(5))\nprint(process(-3))"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 10\n\nНапишите `withdraw(account, amount)`. До `try` напечатайте `'LOCK'`. В `try`: если `amount <= 0`, бросьте `ValueError('отрицательная сумма')`. Иначе напечатайте `f'списали {amount}'`. В `except ValueError as e`: напечатайте `f'Ошибка: {e}'`. В `finally`: напечатайте `'UNLOCK'`. Вызовите для `('A', 500)` и `('B', -10)`."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   LOCK\n#   списали 500\n#   UNLOCK\n#   LOCK\n#   Ошибка: отрицательная сумма\n#   UNLOCK"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 11. UnboundLocalError в finally\n\n*Раздел C*\n\nЕсли переменная ресурса не успела создаться в `try`, в `finally` обращение к ней даёт `UnboundLocalError`. Инициализируйте переменную `None` до `try` и проверяйте в `finally`."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "def connect():\n    conn = None\n    try:\n        raise ConnectionRefusedError(\"упало\")\n        conn = \"fake-connection\"\n    except ConnectionRefusedError as e:\n        print(f\"EXCEPT {e}\")\n    finally:\n        if conn is not None:\n            print(\"закрываем\")\n        else:\n            print(\"нечего закрывать\")\n\nconnect()"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 11\n\nДан код:\n\n```python\ndef read_file():\n    try:\n        raise FileNotFoundError('нет файла')\n        file = open('data.txt')\n    except FileNotFoundError as e:\n        print(f'EXCEPT {e}')\n    finally:\n        file.close()\n```\n\nОн падает с `UnboundLocalError` в `finally`. Исправьте: инициализируйте `file = None` до `try`, в `finally` вызывайте `file.close()` только если `file is not None`. Иначе напечатайте `'нечего закрывать'`. Запустите."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   EXCEPT нет файла\n#   нечего закрывать"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 12. raise по бизнес-правилу\n\n*Раздел D*\n\n`raise` переводит бизнес-правило в исключение. Лучше падать с понятным типом и сообщением, чем тихо возвращать `False`."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "def validate_age(age: int) -> None:\n    if age < 0:\n        raise ValueError(f\"Возраст < 0: {age}\")\n    if age > 150:\n        raise ValueError(f\"Возраст > 150: {age}\")\n\nfor v in [25, -1, 200]:\n    try:\n        validate_age(v)\n        print(f\"OK: {v}\")\n    except ValueError as e:\n        print(f\"Отклонено: {e}\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 12\n\nНапишите `validate_payment(sender, recipient, amount)`. Бросьте `ValueError(f'Сумма <= 0: {amount}')`, если `amount <= 0`. Бросьте `ValueError(f'Перевод на тот же счёт: {sender}')`, если `sender == recipient`. Бросьте `PermissionError(f'Лимит превышен: {amount}')`, если `amount > 1_000_000`. Проверьте на трёх случаях: `('A', 'B', 500)`, `('A', 'A', 100)`, `('A', 'B', 5_000_000)`. При успехе печатайте `'OK'`, при ошибке `f'[{type(e).__name__}] {e}'`."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   OK\n#   [ValueError] Перевод на тот же счёт: A\n#   [PermissionError] Лимит превышен: 5000000"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 13. with: свой класс\n\n*Раздел E*\n\nКласс с `__enter__` и `__exit__` работает с `with`. `__enter__` вызывается при входе, `__exit__` при выходе (всегда, даже при исключении)."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "class Greeter:\n    def __init__(self, name):\n        self.name = name\n    def __enter__(self):\n        print(f\"привет, {self.name}\")\n        return self\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        print(f\"пока, {self.name}\")\n        return False\n\nwith Greeter(\"Aziz\"):\n    print(\"работаем\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 13\n\nНапишите класс `Section(name)`. В `__enter__` напечатайте `f'>>> {self.name}'`. В `__exit__` напечатайте `f'<<< {self.name}'`, верните `False`. Используйте: `with Section('PAYMENT'):` с командой `print('обработка')` внутри."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   >>> PAYMENT\n#   обработка\n#   <<< PAYMENT"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 14. @contextmanager\n\n*Раздел E*\n\nДекоратор `@contextmanager` из `contextlib` делает менеджер из генератора. Код до `yield` это `__enter__`, после `yield` это `__exit__`."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "from contextlib import contextmanager\n\n@contextmanager\ndef section(name):\n    print(f\">>> START {name}\")\n    try:\n        yield\n    finally:\n        print(f\">>> END {name}\")\n\nwith section(\"payment\"):\n    print(\"работаем\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 14\n\nНапишите через `@contextmanager` функцию `redis_lock(key)`. До `yield` напечатайте `f'LOCK {key}'`. В `finally` после `yield` напечатайте `f'UNLOCK {key}'`. Используйте: `with redis_lock('account:42'):` с командой `print('обрабатываем счёт')` внутри."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   LOCK account:42\n#   обрабатываем счёт\n#   UNLOCK account:42"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## 15. logging: настройка и уровни\n\n*Раздел F*\n\nЛоггер настраивается один раз: получаем по имени, задаём уровень, добавляем обработчик с форматом. Дальше вызываем `logger.debug/info/warning/error/critical`."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Пример**. Запустите и разберите вывод:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "import logging\nimport sys\n\nlogger = logging.getLogger(\"demo_drill15_example\")\nlogger.handlers.clear()\nlogger.setLevel(logging.DEBUG)\nhandler = logging.StreamHandler(sys.stdout)\nhandler.setFormatter(logging.Formatter('[%(levelname)s] %(message)s'))\nlogger.addHandler(handler)\nlogger.propagate = False\n\nlogger.info(\"Сервис запущен\")\nlogger.warning(\"Медленный запрос\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "### Задание 15\n\nНастройте логгер `payment_logger` (StreamHandler в `sys.stdout`, формат `'[%(levelname)s] %(message)s'`, уровень `DEBUG`, `propagate = False`, перед `addHandler` сделайте `handlers.clear()`). Вызовите все пять уровней (`debug`, `info`, `warning`, `error`, `critical`) с любыми сообщениями."
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# ваш код здесь\n\n\n# Ожидаемый вывод:\n#   [DEBUG] Начало обработки\n#   [INFO] TX обработана\n#   [WARNING] Медленный запрос\n#   [ERROR] TX отклонена\n#   [CRITICAL] База недоступна"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Финальная задача: Платёжный процессор\n\nСоберите процессор платежей, который:\n\n1. Не падает на любом мусоре во входных данных.\n2. Классифицирует ошибки по категориям:\n   - **Структура** (`StructureError`): данные битые: `None`, нет полей, плохие типы.\n   - **Бизнес** (`BusinessError`): нарушены правила: перевод на тот же счёт, превышен лимит.\n   - **Комплаенс** (`ComplianceError`): получатель в санкционном списке.\n3. Логирует каждую ошибку через `logger.warning` (структура и бизнес) или `logger.error` (комплаенс).\n4. Возвращает отчёт со счётчиками по каждой категории.\n\nЛимит: `1_000_000`. Санкции: `{\"SANCTION_ENTITY\"}`.\n\n**Ожидаемый итоговый отчёт**: `{'success': 2, 'structure': 3, 'business': 2, 'compliance': 1}`."
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Настройка**. Запустите эту ячейку как есть:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "import logging\nimport sys\n\nlogger = logging.getLogger(\"processor\")\nlogger.handlers.clear()\nlogger.setLevel(logging.INFO)\nh = logging.StreamHandler(sys.stdout)\nh.setFormatter(logging.Formatter('[%(levelname)s] %(name)s: %(message)s'))\nlogger.addHandler(h)\nlogger.propagate = False\n\nMAX_LIMIT = 1_000_000\nSANCTIONED = {\"SANCTION_ENTITY\"}\n\nRAW = [\n    {\"id\": \"P1\", \"from\": \"A\", \"to\": \"B\", \"amount\": \"500\"},               # OK\n    {\"id\": \"P2\", \"from\": \"C\", \"amount\": \"300\"},                          # нет 'to'\n    {\"id\": \"P3\", \"from\": \"D\", \"to\": \"D\", \"amount\": \"100\"},               # сам себе\n    {\"id\": \"P4\", \"from\": \"E\", \"to\": \"F\", \"amount\": \"abc\"},               # битая сумма\n    {\"id\": \"P5\", \"from\": \"G\", \"to\": \"H\", \"amount\": \"2000000\"},           # лимит\n    {\"id\": \"P6\", \"from\": \"I\", \"to\": \"SANCTION_ENTITY\", \"amount\": \"100\"}, # санкции\n    None,                                                                # не словарь\n    {\"id\": \"P7\", \"from\": \"J\", \"to\": \"K\", \"amount\": \"750\"},               # OK\n]"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Решение**. Заполните `process_payment` и `process_batch`:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "# TODO: классы StructureError(Exception), BusinessError(Exception), ComplianceError(Exception)\n\n\ndef process_payment(p):\n    # TODO: проверка структуры (raise StructureError)\n    # TODO: проверка бизнес-правил (raise BusinessError)\n    # TODO: проверка санкций (raise ComplianceError)\n    return \"OK\"\n\n\ndef process_batch(payments):\n    report = {\"success\": 0, \"structure\": 0, \"business\": 0, \"compliance\": 0}\n    # TODO: пройти по платежам, поймать каждое исключение,\n    #       обновить счётчик и залогировать\n    return report"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "**Проверка**. Запустите после реализации:"
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": "result = process_batch(RAW)\nprint(result)"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "Готово. Если в отчёте `{'success': 2, 'structure': 3, 'business': 2, 'compliance': 1}` и в логах видны все шесть инцидентов с правильной классификацией: финальная задача сдан."
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}