0027. Единая модель отзывов и вопросов (autoreply)
Копия ADR из репозитория:
backend/docs/adr/0027-autoreply-feedback-model.md. Канонический текст — в репозитории; портал обновляется командойnpm run sync:adr.
- Статус: accepted
- Дата: 2026-06-26
- Связанные задачи: OP-71 (5.5 · Автоответы на отзывы и вопросы)
Контекст и проблема
ТЗ 5.5: импортировать из Wildberries и хранить отзывы и вопросы покупателей.
Описание тикета OP-71 называет «Сущности Review/Question». WB отдаёт их разными
эндпоинтами (GET /api/v1/feedbacks, GET /api/v1/questions), но структура
элементов почти идентична: id, text, createdDate, state, answer,
productDetails (артикул/бренд/товар), wasViewed. Отличие — только рейтинг
(productValuation) есть у отзывов и отсутствует у вопросов.
Вопрос: моделировать двумя таблицами (Review + Question) или одной с дискриминатором?
Рассмотренные варианты
| Вариант | Описание | Оценка |
|---|---|---|
Единая FeedbackEntity + kind (выбрано) | Одна таблица autoreply_feedbacks, поле kind = review | question, rating nullable (только отзывы) | Нет дублирования таблицы/миграции/репозитория; общий импорт и upsert; downstream-модули (сценарии, генерация, отправка) работают с одной сущностью |
| Две сущности Review/Question | Отдельные таблицы и репозитории | Дублирование ~90% полей, импорта и индексов; два пути upsert; усложняет последующие фичи (выборка «всей обратной связи по артикулу») |
| Единая таблица + JSON-payload | Хранить сырой ответ WB в jsonb | Теряется типизация и индексы по полям (артикул, рейтинг, статус) |
Решение
- Одна таблица
autoreply_feedbacksсkind(FeedbackKind), идемпотентность — UNIQUE(wb_account_id, kind, wb_id). - Общие поля для обоих типов;
ratingnullable заполняется только для отзывов. - Импорт обоих видов — одной generic-функцией (
importKind) с разным путём/экстрактором/маппером.
Последствия
- Downstream-тикеты автоответов (OP-72..OP-75) строятся на единой сущности.
- Known limitation: OP-71 импортирует только неотвеченные (
isAnswered=false) отзывы/вопросы, поэтомуanswered/answer_textпри текущем единственном пути импорта всегдаfalse/null. Эти поля — задел под отправку ответов (OP-74), которая будет проставлять статус ответа; импорт уже отвеченной обратной связи — отдельная задача. - При расхождении жизненного цикла отзывов и вопросов в будущем (разные поля или переходы статусов) модель можно разнести — стоимость миграции учтена осознанно.