- app/services/ym.py: клиент Partner API — getGoodsFeedbacks с пагинацией и фильтром по дате, updateGoodsFeedbackComment (ответ), deleteGoodsFeedbackComment (удаление), обработка новых отзывов по аналогии с WB/Ozon - воркер ym_worker, включается при заданных YM_API_KEY и YM_BUSINESS_ID - кнопки отправки/удаления ответа, платформа в сообщении и в таблице, статистика /stat и /allstat - переменные YM_API_KEY, YM_BUSINESS_ID, YM_CHECK_INTERVAL, PRODUCTS_YM_SHEET_LINK - README и .env.example Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
108 lines
7.1 KiB
Python
108 lines
7.1 KiB
Python
from datetime import timedelta, timezone
|
|
|
|
from envparse import env
|
|
|
|
env.read_envfile()
|
|
|
|
BOT_TOKEN = env.str("BOT_TOKEN", default="")
|
|
TG_PROXY = env.str("TG_PROXY", default="")
|
|
TG_CHAT_ID = env.int("TG_CHAT_ID", default=0)
|
|
GOOD_REVIEWS_THREAD_ID = env.int("GOOD_REVIEWS_THREAD_ID", default=0)
|
|
BAD_REVIEWS_THREAD_ID = env.int("BAD_REVIEWS_THREAD_ID", default=0)
|
|
# Топики для отзывов без текста (только оценка): 1–3 звезды и 4–5 звезд
|
|
EMPTY_LOW_RATING_THREAD_ID = env.int("EMPTY_LOW_RATING_THREAD_ID", default=0)
|
|
EMPTY_HIGH_RATING_THREAD_ID = env.int("EMPTY_HIGH_RATING_THREAD_ID", default=0)
|
|
NEGATIVE_RATING = env.int("NEGATIVE_RATING", default=3)
|
|
|
|
REVIEWS_SHEET_LINK = env.str("REVIEWS_SHEET_LINK", default="")
|
|
|
|
PRODUCTS_WB_SHEET_LINK = env.str("PRODUCTS_WB_SHEET_LINK", default="")
|
|
PRODUCTS_OZON_SHEET_LINK = env.str("PRODUCTS_OZON_SHEET_LINK", default="")
|
|
PRODUCTS_YM_SHEET_LINK = env.str("PRODUCTS_YM_SHEET_LINK", default="")
|
|
PRODUCTS_CACHE_TTL = env.int("PRODUCTS_CACHE_TTL", default=60 * 5)
|
|
|
|
# Ключ сервисного аккаунта Google: либо содержимое JSON (или его base64)
|
|
# в переменной окружения, либо путь к файлу.
|
|
GOOGLE_SERVICE_ACCOUNT_JSON = env.str("GOOGLE_SERVICE_ACCOUNT_JSON", default="")
|
|
GOOGLE_SERVICE_ACCOUNT_FILE = env.str(
|
|
"GOOGLE_SERVICE_ACCOUNT_FILE", default="google-auth/key.json"
|
|
)
|
|
|
|
OZON_API_KEY = env.str("OZON_API_KEY", default="")
|
|
OZON_CLIENT_ID = env.str("OZON_CLIENT_ID", default="")
|
|
WB_API_KEY = env.str("WB_API_KEY", default="")
|
|
# Яндекс Маркет: Api-Key и идентификатор кабинета (businessId)
|
|
YM_API_KEY = env.str("YM_API_KEY", default="")
|
|
YM_BUSINESS_ID = env.int("YM_BUSINESS_ID", default=0)
|
|
GPTUNNEL_API_KEY = env.str("GPTUNNEL_API_KEY", default="")
|
|
|
|
# Название бренда, от имени которого бот анализирует отзывы и отвечает на них
|
|
BRAND_NAME = env.str("BRAND_NAME", default="Magic Stories")
|
|
|
|
OZON_BASE_URL = "https://api-seller.ozon.ru"
|
|
WB_BASE_URL = "https://feedbacks-api.wildberries.ru"
|
|
YM_BASE_URL = "https://api.partner.market.yandex.ru"
|
|
GPT_API_URL = "https://gptunnel.ru/v1/chat/completions"
|
|
|
|
OZON_CHECK_INTERVAL = env.int("OZON_CHECK_INTERVAL", default=60)
|
|
WB_CHECK_INTERVAL = env.int("WB_CHECK_INTERVAL", default=300)
|
|
YM_CHECK_INTERVAL = env.int("YM_CHECK_INTERVAL", default=300)
|
|
|
|
ANALYSIS_PROMPT = f"""
|
|
Вы - представитель бренда {BRAND_NAME} и анализируете отзывы клиентов.
|
|
Классифицируйте отзыв по следующим критериям:
|
|
|
|
1. Тональность отзыва (позитивная, нейтральная, негативная).
|
|
|
|
2. Причина тональности:
|
|
- Для негативных отзывов одна из категорий: Эргонолептика, Качество, Повреждённая упаковка, Недокомплект, Маркировка, Цена, Недовес, Проблемы со здоровьем, Жалобы на ЖКТ, Инородное тело, Жалобы на эффект, Жалобы на доставку, Общее, Эффект.
|
|
- Для позитивных отзывов одна из категорий: Эргонолептика, Качество товара, Упаковка, Комплектация, Цена, Польза для здоровья, Эффективность, Доставка, Общее, Эффект.
|
|
- Для нейтральных отзывов причина не требуется.
|
|
|
|
3. Критичность
|
|
Отзыв считается критичным, если он классифицируется как любой тип из списка:
|
|
- Здоровье, Жалобы на Жкт, Недокомплект(Недовес), Поврежденная упаковка, Инородное тело, Маркировка
|
|
|
|
Ответ должен быть СТРОГО в формате JSON:
|
|
```json
|
|
{{
|
|
"tone": "positive|negative|neutral",
|
|
"reason": "причина тональности из списка или пустая строка для нейтральных отзывов",
|
|
"is_critical": false // true если отзыв критичный
|
|
}}
|
|
```
|
|
|
|
ОБЯЗАТЕЛЬНО используйте ТОЛЬКО указанные категории из списка. Никаких других категорий или вариантов.
|
|
""".strip()
|
|
|
|
ANSWER_PROMPT = f"""
|
|
Вы — профессиональный работник службы поддержки с многолетним опытом и чутким пониманием психологии человека,
|
|
официальный представитель бренда {BRAND_NAME}. Ваша задача — вежливо и профессионально отвечать на отзывы клиентов.
|
|
Ответ должен быть коротким, информативным и соответствовать тональности отзыва:
|
|
- Негативный отзыв: выразите сожаление за произошедшее, предложите решение или помощь, объясните, как можно исправить ситуацию.
|
|
- Позитивный отзыв: искренне поблагодарите за выбор бренда и положительный опыт, подтвердив, что вам важна обратная связь.
|
|
- Нейтральный отзыв: подтвердите получение обратной связи и предложите дополнительную помощь, если это нужно.
|
|
Не забывайте, что лесть перед клиентом люди хорошо считывают, поэтому избегайте чрезмерных похвал и канцеляризмов.
|
|
Опишите решение проблемы или благодарность за положительный отзыв простым и понятным языком, который будет одинаково понятен любому клиенту. Также не отправляй почту
|
|
В конец отзыва всегда добавляйте "С уважением, команда {BRAND_NAME}." с новой строки.
|
|
""".strip()
|
|
|
|
# Шаблоны автоответа на отзывы без текста (только оценка)
|
|
# 1–3 звезды — низкий рейтинг, 4–5 — высокий
|
|
TEMPLATE_EMPTY_LOW_RATING = "Спасибо за отзыв, извините"
|
|
TEMPLATE_EMPTY_HIGH_RATING = "Спасибо за отзыв, рады что вам понравилось"
|
|
|
|
DB_PATH = env.str("DB_PATH", default="db.sqlite3")
|
|
|
|
TORTOISE_ORM = {
|
|
"connections": {"default": f"sqlite://{DB_PATH}"},
|
|
"apps": {
|
|
"models": {
|
|
"models": ["app.models"],
|
|
"default_connection": "default",
|
|
},
|
|
},
|
|
}
|
|
|
|
TIMEZONE = timezone(timedelta(hours=3))
|