Как мы внедрили ИИ для обработки рекламаций на производстве
Где бы вы ни работали и каким идеальным продуктом или сервисом вы бы ни занимались, вас всегда будут сопровождать жалобы и рекламации от клиентов.
Рекламации — это вежливо-агрессивная форма общения между заказчиком и поставщиком, где каждая сторона добивается максимально приемлемого для себя результата. Потребитель, в идеале, хочет замену товара без дополнительных затрат, а производитель — соблюсти баланс между полным отзывом по гарантийному случаю , или вежливым ответом: «ваше обращение очень важно для нас, но помочь ничем не можем — вот вам промокод в размере 2% на последующие покупки».
С одной стороны, жалобы от клиентов являются неотъемлемой частью развития компании, ведь их отсутствие может коррелировать с отрицательным, как у нас любят говорить, ростом продаж. Но с другой стороны, каждая компания стремится минимизировать их количество, стараясь выработать какой-то базовый чеклист для работы с ними.
Об этом я и хочу написать — опыт одной компании и как она внедрила систему отслеживания рекламаций на производстве с использованием искусственного интеллекта.
В конце, ссылка на весь проект. Копируйте и применяйте на свой бизнес!
Предыстория
Группа компаний Эпотос — один из крупнейших производителей систем пожаротушения в России. Организация уже более 30 лет защищает различные объекты, которые встречаются нам на ежедневной основе — от метрополитена до БЕЛАЗов.
Можно только представить, какой спрос к компаниям, которые занимаются непосредственно нашей с вами безопасностью.
Как устроена работа с рекламациями в компании сегодня?
Есть разные направления, с которыми работает компания: Спецтехника, ВЭД, Наземный транспорт и так далее. Все они имеют свои чеклисты по работе с клиентами в случае получения рекламаций.
В прошлом году количество обращений выросло кратно и порой составляло несколько десятков в неделю. Связано это с тем, что краткосрочных задач много, а эксперты по рекламациям не успевали анализировать более глубоко первопричины возникшей проблемы.
Все рекламации поступают на почту компании info@company.ru, куда скидываются и коммерческие приглашения от других дилеров, и приглашения на выставки. По старинке, секретариат пересылал вручную письма и регистрировал входящие рекламации. У сотрудников, спустя время, начинает формироваться полотно из бесконечной переписки с клиентом по их жалобе.
Все рекламации фиксировались в Excel-таблице с довольно удобной разметкой, но она заполнялась вручную, в облаке, со всеми статусами и обновлениями по прогрессу.
Схема до автоматизации выглядела так:
Что решили сделать?
Чтобы не ломать существующий процесс (а это один из самых важных составляющих, когда речь заходит о цифровизации и автоматизации), было принято решение составить следующую схему работы:
-
Подключаемся по IMAP к почте компании, куда скидываются рекламации. Тем самым, торговые дома и другие потребители имеют тот же привычный канал коммуникации в случае дефекта.
-
Разворачиваем локальный ИИ для работы с данными клиентов, чтобы все результаты обработки хранились на сервере. Приходится работать с данными, которые не сильно хочется посылать в облако. Тем более, когда речь идет о чтении всего почтового ящика компании.
-
Создаём фильтр, какие письма мы считаем рекламациями и по какому направлению эта рекламация должна вестись. ИИ интерпретирует слово «рекламация» по-разному, и вы это увидите в статье. Нужно чётко обозначить рамки того, что именно является жалобой на продукцию.
-
Интегрируем почту с CRM-системой, а именно Bitrix24:
-
Создаём Битрикс-списки и туда фиксируем результат обработки по 25–30 полям. От номера рекламационного акта до текста письма вместе с файлами, которые приложили к жалобе.
-
Создаём бизнес-процесс, который автоматически будет создавать задачу в Битрикс со своим чеклистом, формирующимся в зависимости от категории рекламации. Исполнители и наблюдатели добавляются в задачу в зависимости от направления, кому пришла жалоба.
-
Фиксируем последовательность принятия работы с рекламацией — когда можно закрыть задачу и кем.
-
Даже предварительно, на бумаге скажем так, начинает звучать не так легко, как кажется :)
Схема работы:
Первые приключения
В ходе работы мы читаем не только сырой текст из имейла, но ещё и вложенные файлы внутри письма. Всё это следует прочитать и обработать, а затем послать в ИИ для определения, является ли входящее обращение рекламацией или нет.
Для оцифровки текста из PDF, фото и других форматов, где простым способом не считываются слова и буквы, была выбрана библиотека Tesseract с адаптацией для русского языка. Это самый адекватный способ OCR — другие библиотеки и подходы выдавали не такой качественный результат.
Можете поставить любую OCR-модель по типу Deepseek, но для нашего кейса это ни к чему — не понятно, зачем тратить ресурсы CPU/GPU на это.
Затем выберем модель для работы с рекламациями. В самом начале мы скачали T-lite 7B 1.0. Она является дообученной (fine-tune) моделью Qwen-2.5 с русским датасетом и чем-то ещё внутри (мы не знаем чем).
Но в ходе тестирования мы и сами прокачали свои знания по ИИ, и в целом узнали, что у нас тянет не с самой лучшей скоростью, но прекрасная модель Qwen3-30B-A3B. Сложно найти что-то лучше за такой объём обученной модели, так ещё и с reasoning, и с системой Mixture-of-Experts. Что это значит? ИИ сам применяет на себя образы, какая роль лучше всего подойдёт для ответа на тот или иной запрос, и пользователь получает более точный ответ. Более того, вставка «A3» у модели свидетельствует о том, что при 30 миллиардах обученных параметров ИИ тратит только 3 миллиарда в активной фазе для ответа на вопрос. Звучит слишком хорошо, чтобы быть правдой, но модель и вправду очень хороша.
Готовимся к запуску
1. Устанавливаем ИИ локально
Покажу, как установить ИИ. Сейчас это делается довольно просто. Фреймворк Ollama довёл этот процесс до совершенства и даже добавил возможность запустить модель в локальном ChatGPT-подобном интерфейсе, чтобы мучать её на своих мощностях.
Итак, скачиваем и запускаем:
# Установка 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:
-
Узнаёте IMAP-адрес вашего почтового сервера (обычно
imap.domain.ru, порт993, SSL). -
Создаёте отдельную учётку или берёте пароль приложения — не пароль от личного кабинета.
-
Подключаетесь через
imaplib(встроена в Python, ничего ставить не нужно). -
Выбираете папку
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. Соединяем с Битрикс
Соединяем всё с Битрикс-списком по входящему вебхуку. Его надо создать с правами администратора, иначе ничего не выйдет.
Извините, но скрин будет выглядеть как файл из дела об Эпштейне, потому что я не могу с вами делиться этими данными.
Делаем затем бизнес-процесс, чтобы автоматически создавать задачу в CRM с нужными нам ответственными и уникальными чеклистами на закрытие задачи.
Как я писал выше, чеклисты всегда существовали где-то в уме или устно передавались из уст в уста новоприбывшим сотрудникам. Но нигде порядок действий не был зафиксирован в цифровом или другом виде. Было очень сложно вытягивать эту информацию у разных отделов, но у нас получилось!
7. Админка
Я периодически отсутствую на рабочем месте из-за командировок. Или же я просто хочу отдохнуть в выходные не в офисе, а дома (удивительно, правда?). Секретариат всё ещё внимательно отслеживает каждое письмо, пришедшее на почтовый ящик, и так произошло, что на первых порах сервис по анализу с ИИ останавливался сам собой. Для этого я создал свою админку с блекджеком и Клодом, и вот что из этого вышло:
(Все данные выдуманные, а то мне прилетит по бошке)
Главный экран — список рекламаций
Тут можно и за конкретную дату запустить анализ по рекламациям (как вы поняли, сервис на какое-то количество суток падал, и секретариат вручную по старинке отправлял письма, но задачи в Битрикс уже надо фиксировать).
Чего мы добились?
Автоматизация жалоб от потребителей дала возможность хранить цепочку информации по жизненному циклу продукции. Это уже не просто «завести дату маркировки серийного номера и забыть, зачем мы это сделали», а целая сеть с причинно-следственной связью — что за чем идёт и почему так произошло.
Конечно, теперь дашборды на совещаниях по рекламациям рисовать удобнее, но самое важное — получилось ещё и стандартизировать чеклист по работе с обращениями от клиентов. Теперь новые сотрудники не будут стесняться спрашивать, что делать в той или иной ситуации, а просто посмотрят на список подзадач, который сформировался за годы работы их коллег.
Ну и банально — теперь есть контроль за работой над жалобой в режиме реального времени, что тоже неплохо.
Отдельное спасибо ГК Эпотос, что позволили рассказать про интересный кейс, как можно оцифровывать такой важный процесс, как рекламации с помощью ИИ!
Буду рад за лайк и подписку на канал :) https://t.me/notes_from_cto
Наш сайт: https://bvmax.ru/ai
Сайт ГК Эпотос: https://epotos.ru
Ссылка на открытый проект: https://github.com/Chashchin-Dmitry/reclamation-monitor/tree/main
Автор: Dmitrii-Chashchin

