Как мы внедрили ИИ для обработки рекламаций на производстве

Где бы вы ни работали и каким идеальным продуктом или сервисом вы бы ни занимались, вас всегда будут сопровождать жалобы и рекламации от клиентов.

Рекламации — это вежливо-агрессивная форма общения между заказчиком и поставщиком, где каждая сторона добивается максимально приемлемого для себя результата. Потребитель, в идеале, хочет замену товара без дополнительных затрат, а производитель — соблюсти баланс между полным отзывом по гарантийному случаю , или вежливым ответом: «ваше обращение очень важно для нас, но помочь ничем не можем — вот вам промокод в размере 2% на последующие покупки».

С одной стороны, жалобы от клиентов являются неотъемлемой частью развития компании, ведь их отсутствие может коррелировать с отрицательным, как у нас любят говорить, ростом продаж. Но с другой стороны, каждая компания стремится минимизировать их количество, стараясь выработать какой-то базовый чеклист для работы с ними.

Об этом я и хочу написать — опыт одной компании и как она внедрила систему отслеживания рекламаций на производстве с использованием искусственного интеллекта.

В конце, ссылка на весь проект. Копируйте и применяйте на свой бизнес!

Предыстория

Группа компаний Эпотос — один из крупнейших производителей систем пожаротушения в России. Организация уже более 30 лет защищает различные объекты, которые встречаются нам на ежедневной основе — от метрополитена до БЕЛАЗов.

Фото сотрудников Эпотос на фоне завода

Фото сотрудников Эпотос на фоне завода

Можно только представить, какой спрос к компаниям, которые занимаются непосредственно нашей с вами безопасностью.

Как устроена работа с рекламациями в компании сегодня?

Есть разные направления, с которыми работает компания: Спецтехника, ВЭД, Наземный транспорт и так далее. Все они имеют свои чеклисты по работе с клиентами в случае получения рекламаций.

В прошлом году количество обращений выросло кратно и порой составляло несколько десятков в неделю. Связано это с тем, что краткосрочных задач много, а эксперты по рекламациям не успевали анализировать более глубоко первопричины возникшей проблемы.

Все рекламации поступают на почту компании info@company.ru, куда скидываются и коммерческие приглашения от других дилеров, и приглашения на выставки. По старинке, секретариат пересылал вручную письма и регистрировал входящие рекламации. У сотрудников, спустя время, начинает формироваться полотно из бесконечной переписки с клиентом по их жалобе.

Все рекламации фиксировались в Excel-таблице с довольно удобной разметкой, но она заполнялась вручную, в облаке, со всеми статусами и обновлениями по прогрессу.

Схема до автоматизации выглядела так:

Изображение 1. Диаграмма работы с рекламациями до ИИ

Изображение 1. Диаграмма работы с рекламациями до ИИ

Что решили сделать?

Чтобы не ломать существующий процесс (а это один из самых важных составляющих, когда речь заходит о цифровизации и автоматизации), было принято решение составить следующую схему работы:

  1. Подключаемся по IMAP к почте компании, куда скидываются рекламации. Тем самым, торговые дома и другие потребители имеют тот же привычный канал коммуникации в случае дефекта.

  2. Разворачиваем локальный ИИ для работы с данными клиентов, чтобы все результаты обработки хранились на сервере. Приходится работать с данными, которые не сильно хочется посылать в облако. Тем более, когда речь идет о чтении всего почтового ящика компании.

  3. Создаём фильтр, какие письма мы считаем рекламациями и по какому направлению эта рекламация должна вестись. ИИ интерпретирует слово «рекламация» по-разному, и вы это увидите в статье. Нужно чётко обозначить рамки того, что именно является жалобой на продукцию.

  4. Интегрируем почту с CRM-системой, а именно Bitrix24:

    • Создаём Битрикс-списки и туда фиксируем результат обработки по 25–30 полям. От номера рекламационного акта до текста письма вместе с файлами, которые приложили к жалобе.

    • Создаём бизнес-процесс, который автоматически будет создавать задачу в Битрикс со своим чеклистом, формирующимся в зависимости от категории рекламации. Исполнители и наблюдатели добавляются в задачу в зависимости от направления, кому пришла жалоба.

    • Фиксируем последовательность принятия работы с рекламацией — когда можно закрыть задачу и кем.

Даже предварительно, на бумаге скажем так, начинает звучать не так легко, как кажется :)

Схема работы:

Изображение 2. Логика работы с рекламациями после внедрения ИИ и Битрикс

Изображение 2. Логика работы с рекламациями после внедрения ИИ и Битрикс

Первые приключения

В ходе работы мы читаем не только сырой текст из имейла, но ещё и вложенные файлы внутри письма. Всё это следует прочитать и обработать, а затем послать в ИИ для определения, является ли входящее обращение рекламацией или нет.

Для оцифровки текста из PDF, фото и других форматов, где простым способом не считываются слова и буквы, была выбрана библиотека Tesseract с адаптацией для русского языка. Это самый адекватный способ OCR — другие библиотеки и подходы выдавали не такой качественный результат.

https://github.com/tesseract-ocr/tesseract

Можете поставить любую OCR-модель по типу Deepseek, но для нашего кейса это ни к чему — не понятно, зачем тратить ресурсы CPU/GPU на это.

Изображение 3. Скрин Tesseract OCR

Изображение 3. Скрин Tesseract OCR

Затем выберем модель для работы с рекламациями. В самом начале мы скачали T-lite 7B 1.0. Она является дообученной (fine-tune) моделью Qwen-2.5 с русским датасетом и чем-то ещё внутри (мы не знаем чем).

https://huggingface.co/t-tech/T-lite-it-1.0

Изображение 4. Модель t-lite-1.0

Изображение 4. Модель t-lite-1.0

Но в ходе тестирования мы и сами прокачали свои знания по ИИ, и в целом узнали, что у нас тянет не с самой лучшей скоростью, но прекрасная модель Qwen3-30B-A3B. Сложно найти что-то лучше за такой объём обученной модели, так ещё и с reasoning, и с системой Mixture-of-Experts. Что это значит? ИИ сам применяет на себя образы, какая роль лучше всего подойдёт для ответа на тот или иной запрос, и пользователь получает более точный ответ. Более того, вставка «A3» у модели свидетельствует о том, что при 30 миллиардах обученных параметров ИИ тратит только 3 миллиарда в активной фазе для ответа на вопрос. Звучит слишком хорошо, чтобы быть правдой, но модель и вправду очень хороша.

https://huggingface.co/Qwen/Qwen3-30B-A3B

Изображение 5. Модель Qwen3-30B-A3B

Изображение 5. Модель Qwen3-30B-A3B

Готовимся к запуску

1. Устанавливаем ИИ локально

Покажу, как установить ИИ. Сейчас это делается довольно просто. Фреймворк Ollama довёл этот процесс до совершенства и даже добавил возможность запустить модель в локальном ChatGPT-подобном интерфейсе, чтобы мучать её на своих мощностях.

Изображение 6. Интерфейс Ollama

Изображение 6. Интерфейс Ollama

Итак, скачиваем и запускаем:

# Установка Ollama (macOS / Linux)
curl -fsSL https://ollama.com/install.sh | sh

# Запускаем сервер (если не стартанул автоматом)
ollama serve

# Скачиваем модель
ollama pull qwen3:30b-a3b

# Проверяем, что модель на месте
ollama list

# Запускаем в интерактивном режиме для теста
ollama run qwen3:30b-a3b

Делаем первые тестовые запросы:

# Через API
curl http://localhost:11434/api/generate -d '{
  "model": "qwen3:30b-a3b",
  "prompt": "Что такое рекламация в контексте производства?",
  "stream": false
}'
# Или через Python
import requests

response = requests.post("http://localhost:11434/api/generate", json={
    "model": "qwen3:30b-a3b",
    "prompt": "Что такое рекламация в контексте производства?",
    "stream": False
})
print(response.json()["response"])

Запуском ИИ у себя на ноутбуке уже никого не удивишь, поэтому мы должны идти дальше.

2. Подключаемся к почте по IMAP

Чтобы читать, что пишут нам разного рода люди и не только. Базовая инструкция, как подключиться к IMAP:

  1. Узнаёте IMAP-адрес вашего почтового сервера (обычно imap.domain.ru, порт 993, SSL).

  2. Создаёте отдельную учётку или берёте пароль приложения — не пароль от личного кабинета.

  3. Подключаетесь через imaplib (встроена в Python, ничего ставить не нужно).

  4. Выбираете папку INBOX и фильтруете по дате — чтобы не перечитывать всю историю каждый раз.

import imaplib
import email
from email.header import decode_header

# Подключение
mail = imaplib.IMAP4_SSL("imap.your-server.ru", 993)
mail.login("info@your-domain.ru", "your-app-password")
mail.select("INBOX")

# Ищем письма за конкретную дату
status, messages = mail.search(None, '(SINCE "10-Mar-2026")')
mail_ids = messages[0].split()

print(f"Найдено писем: {len(mail_ids)}")

# Читаем каждое письмо
for mail_id in mail_ids:
    status, msg_data = mail.fetch(mail_id, "(RFC822)")
    raw_email = msg_data[0][1]
    msg = email.message_from_bytes(raw_email)

    # Тема
    subject, encoding = decode_header(msg["Subject"])[0]
    if isinstance(subject, bytes):
        subject = subject.decode(encoding or "utf-8")

    # Отправитель
    sender = msg.get("From")

    print(f"От: {sender} | Тема: {subject}")

mail.logout()

3. Женим ИИ и почту

Вычленяем текст и файлы из письма, прогоняем через OCR при необходимости, и отправляем в локальную модель:

import os
import pytesseract
from PIL import Image
from PyPDF2 import PdfReader
import requests
import tempfile

OLLAMA_URL = "http://localhost:11434/api/generate"
MODEL = "qwen3:30b-a3b"


def extract_text_from_attachment(part):
    """Извлекаем текст из вложения письма."""
    filename = part.get_filename()
    content_type = part.get_content_type()
    payload = part.get_payload(decode=True)

    if not payload or not filename:
        return ""

    with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(filename)[1]) as tmp:
        tmp.write(payload)
        tmp_path = tmp.name

    text = ""
    try:
        if content_type == "application/pdf":
            reader = PdfReader(tmp_path)
            text = "n".join(page.extract_text() or "" for page in reader.pages)

        elif content_type.startswith("image/"):
            img = Image.open(tmp_path)
            text = pytesseract.image_to_string(img, lang="rus")

        elif "text" in content_type or filename.endswith((".txt", ".csv")):
            text = payload.decode("utf-8", errors="ignore")
    finally:
        os.unlink(tmp_path)

    return text


def get_email_full_text(msg):
    """Собираем весь текст из письма: тело + вложения."""
    body = ""
    attachments_text = []

    for part in msg.walk():
        content_type = part.get_content_type()

        if content_type == "text/plain" and not part.get_filename():
            charset = part.get_content_charset() or "utf-8"
            body = part.get_payload(decode=True).decode(charset, errors="ignore")

        elif part.get_filename():
            att_text = extract_text_from_attachment(part)
            if att_text.strip():
                attachments_text.append(f"[Файл: {part.get_filename()}]n{att_text}")

    full_text = body
    if attachments_text:
        full_text += "nn--- ВЛОЖЕНИЯ ---n" + "nn".join(attachments_text)

    return full_text


def ask_ollama(prompt, system_prompt=""):
    """Отправляем запрос в локальную модель."""
    payload = {
        "model": MODEL,
        "prompt": prompt,
        "stream": False,
    }
    if system_prompt:
        payload["system"] = system_prompt

    response = requests.post(OLLAMA_URL, json=payload)
    return response.json().get("response", "")

4. Система промтов

Составим промты для анализа. Вот система — какой за чем идёт и когда применяется:

Промт 1 — Классификация письма. Определяем, является ли письмо рекламацией вообще:

SYSTEM_PROMPT = """Ты — специалист по анализу документов. Правила:
1. Один физический продукт = одна запись в products
2. Если продукт упоминается несколько раз — объедини в одну запись
3. Категория: определи по контексту (Наземка/Метро/Спецтехника/ЖДТ)"""

CLASSIFICATION_PROMPT = """Ты — специалист по анализу технической документации ЭПОТОС.

ДОКУМЕНТ: {filename}
ТИП ФАЙЛА: {content_type}

ПОЛНЫЙ ТЕКСТ ДОКУМЕНТА:
{full_text}

КОНТЕКСТ ПИСЬМА:
- Тема: {email_subject}
- Отправитель: {email_sender}
- Дата: {email_date}

ЗАДАЧА: Определи, является ли это письмо рекламацией на продукцию ЭПОТОС.

Рекламация — это жалоба на дефект, неисправность, несоответствие продукции.
НЕ рекламация — коммерческое предложение, приглашение, счёт, спам, внутренняя переписка.

Ответь строго в формате JSON:
{
  "is_reclamation": true/false,
  "confidence": 0.0-1.0,
  "reason": "краткое обоснование"
}"""

Промт 2 — Детальный разбор. Если письмо признано рекламацией, вытаскиваем структурированные данные:

EXTRACTION_PROMPT = """Это письмо является рекламацией. Извлеки данные:

ТЕКСТ ПИСЬМА:
{full_text}

Ответь строго в формате JSON:
{
  "products": [
    {
      "name": "название изделия",
      "serial_number": "серийный номер если есть",
      "quantity": 1,
      "defect_description": "описание дефекта"
    }
  ],
  "category": "Наземка/Метро/Спецтехника/ЖДТ",
  "sender_organization": "название организации отправителя",
  "contact_person": "контактное лицо",
  "reclamation_act_number": "номер акта если указан",
  "severity": "критическая/средняя/низкая",
  "summary": "краткое описание проблемы в 1-2 предложениях"
}"""

4 (бонус). Фильтр внутренних писем

Важный момент: уже в ходе тестирования мы узнали, что рабочие внутри Эпотос отправляют ответы на рекламации на ту же почту info@. Поэтому делаем хардкод-фильтр: любые имейлы, пришедшие от доменного имени @epotos, мы игнорируем. За исключением одного сотрудника, потому что ему периодически пересылают жалобы на личный имейл — он работает в компании более 20 лет, и не всегда легко объяснить клиенту, что мы тут, понимаете ли, ИИ-автоматизацией занимаемся, а не делаем непойми что.

INTERNAL_DOMAIN = "@epotos.ru"
EXCEPTION_EMAILS = ["ivanov@epotos.ru"]  # Тот самый сотрудник с 20-летним стажем

def is_internal_email(sender: str) -> bool:
    """Фильтруем внутренние письма, кроме исключений."""
    sender_lower = sender.lower()

    # Проверяем исключения
    for exception in EXCEPTION_EMAILS:
        if exception in sender_lower:
            return False

    # Всё остальное от @epotos — игнорируем
    if INTERNAL_DOMAIN in sender_lower:
        return True

    return False

5. Запускаем процессор

import json
import time
from datetime import datetime

PROCESSED_FILE = "processing_results.json"
CHECK_INTERVAL = 120  # секунд


def process_mailbox(target_date: str):
    """Основной цикл обработки почты."""
    mail = connect_imap()
    mail.select("INBOX")

    status, messages = mail.search(None, f'(SINCE "{target_date}")')
    mail_ids = messages[0].split()

    results = {"processed": 0, "reclamations": 0, "skipped": 0, "errors": 0}

    for mail_id in mail_ids:
        try:
            status, msg_data = mail.fetch(mail_id, "(RFC822)")
            msg = email.message_from_bytes(msg_data[0][1])

            sender = msg.get("From", "")
            subject = decode_header(msg["Subject"])[0][0]
            if isinstance(subject, bytes):
                subject = subject.decode("utf-8", errors="ignore")

            # Фильтр внутренних
            if is_internal_email(sender):
                results["skipped"] += 1
                continue

            # Собираем текст
            full_text = get_email_full_text(msg)

            # Спрашиваем ИИ
            classification = ask_ollama(
                CLASSIFICATION_PROMPT.format(
                    filename="email",
                    content_type="text/plain",
                    full_text=full_text,
                    email_subject=subject,
                    email_sender=sender,
                    email_date=msg.get("Date", ""),
                ),
                system_prompt=SYSTEM_PROMPT,
            )

            result = json.loads(classification)

            if result.get("is_reclamation"):
                # Детальный разбор
                details = ask_ollama(EXTRACTION_PROMPT.format(full_text=full_text))
                reclamation_data = json.loads(details)

                # Отправляем в Битрикс
                create_bitrix_list_entry(reclamation_data, msg)
                results["reclamations"] += 1

            results["processed"] += 1

        except Exception as e:
            results["errors"] += 1
            print(f"Ошибка обработки письма {mail_id}: {e}")

    mail.logout()
    return results


# Бесконечный цикл проверки
while True:
    today = datetime.now().strftime("%d-%b-%Y")
    print(f"[{datetime.now()}] Проверяем почту за {today}...")

    results = process_mailbox(today)
    print(f"Результаты: {results}")

    with open(PROCESSED_FILE, "w") as f:
        json.dump(results, f, ensure_ascii=False, indent=2)

    print(f"Следующая проверка через {CHECK_INTERVAL} секунд")
    time.sleep(CHECK_INTERVAL)

6. Соединяем с Битрикс

Соединяем всё с Битрикс-списком по входящему вебхуку. Его надо создать с правами администратора, иначе ничего не выйдет.

Извините, но скрин будет выглядеть как файл из дела об Эпштейне, потому что я не могу с вами делиться этими данными.

Изображение 7. Пример заполненного Битрикс-списка

Изображение 7. Пример заполненного Битрикс-списка

Делаем затем бизнес-процесс, чтобы автоматически создавать задачу в CRM с нужными нам ответственными и уникальными чеклистами на закрытие задачи.

Как я писал выше, чеклисты всегда существовали где-то в уме или устно передавались из уст в уста новоприбывшим сотрудникам. Но нигде порядок действий не был зафиксирован в цифровом или другом виде. Было очень сложно вытягивать эту информацию у разных отделов, но у нас получилось!

Изображение 8. Пример задачи в Битрикс по рекламации

Изображение 8. Пример задачи в Битрикс по рекламации

7. Админка

Я периодически отсутствую на рабочем месте из-за командировок. Или же я просто хочу отдохнуть в выходные не в офисе, а дома (удивительно, правда?). Секретариат всё ещё внимательно отслеживает каждое письмо, пришедшее на почтовый ящик, и так произошло, что на первых порах сервис по анализу с ИИ останавливался сам собой. Для этого я создал свою админку с блекджеком и Клодом, и вот что из этого вышло:

(Все данные выдуманные, а то мне прилетит по бошке)

Изображение 9. Главный экран — список рекламаций

Изображение 9. Главный экран — список рекламаций

Главный экран — список рекламаций

Изображение 10. Все письма — полный лог обработки

Изображение 10. Все письма — полный лог обработки
Изображение 11. Live логи — что происходит в реальном времени

Изображение 11. Live логи — что происходит в реальном времени
Изображение 12. История запусков — статистика по каждому прогону

Изображение 12. История запусков — статистика по каждому прогону
Изображение 13. Настройки — промты, системный промт, блеклист

Изображение 13. Настройки — промты, системный промт, блеклист

Тут можно и за конкретную дату запустить анализ по рекламациям (как вы поняли, сервис на какое-то количество суток падал, и секретариат вручную по старинке отправлял письма, но задачи в Битрикс уже надо фиксировать).

Чего мы добились?

Автоматизация жалоб от потребителей дала возможность хранить цепочку информации по жизненному циклу продукции. Это уже не просто «завести дату маркировки серийного номера и забыть, зачем мы это сделали», а целая сеть с причинно-следственной связью — что за чем идёт и почему так произошло.

Конечно, теперь дашборды на совещаниях по рекламациям рисовать удобнее, но самое важное — получилось ещё и стандартизировать чеклист по работе с обращениями от клиентов. Теперь новые сотрудники не будут стесняться спрашивать, что делать в той или иной ситуации, а просто посмотрят на список подзадач, который сформировался за годы работы их коллег.

Ну и банально — теперь есть контроль за работой над жалобой в режиме реального времени, что тоже неплохо.

Отдельное спасибо ГК Эпотос, что позволили рассказать про интересный кейс, как можно оцифровывать такой важный процесс, как рекламации с помощью ИИ!


Буду рад за лайк и подписку на канал :) https://t.me/notes_from_cto


Наш сайт: https://bvmax.ru/ai
Сайт ГК Эпотос: https://epotos.ru

Ссылка на открытый проект: https://github.com/Chashchin-Dmitry/reclamation-monitor/tree/main


Автор: Dmitrii-Chashchin

Источник

Оставить комментарий