.
This commit is contained in:
@@ -0,0 +1,922 @@
|
||||
# API Документация: Генерация стратегии продвижения (Frontend/AI Agent)
|
||||
|
||||
## Базовый URL
|
||||
|
||||
```
|
||||
https://api.konturai.kz
|
||||
```
|
||||
|
||||
## Обзор
|
||||
|
||||
API для генерации детальной стратегии продвижения продукта на основе маркетингового анализа. Стратегия включает:
|
||||
|
||||
1. **Недельный план** - темы и рекомендации по контенту для каждой недели
|
||||
2. **Календарь постов** - детальный план публикаций с датами, платформами, текстами и хештегами
|
||||
|
||||
Процесс состоит из двух этапов:
|
||||
|
||||
1. **Запуск генерации стратегии** - создание задачи и начало асинхронной обработки
|
||||
2. **Получение результатов** - проверка статуса и получение готовой стратегии
|
||||
|
||||
Генерация стратегии выполняется асинхронно и занимает примерно 3-5 минут.
|
||||
|
||||
**Важно**: Для генерации стратегии требуется завершенный маркетинговый анализ. Сначала необходимо получить `analysisId` из завершенного анализа.
|
||||
|
||||
---
|
||||
|
||||
## Эндпоинты
|
||||
|
||||
### 1. Запуск генерации стратегии продвижения
|
||||
|
||||
**POST** `/api/marketing/strategy/generate`
|
||||
|
||||
Создает новую задачу на генерацию стратегии продвижения и запускает асинхронную обработку.
|
||||
|
||||
#### Параметры запроса
|
||||
|
||||
| Параметр | Тип | Расположение | Обязательный | Описание |
|
||||
| ------------------- | ------- | ------------ | ------------ | --------------------------------------- |
|
||||
| `analysisId` | string | Query | ✅ | ID завершенного маркетингового анализа |
|
||||
| `durationWeeks` | integer | Body | ❌ | Длительность стратегии в неделях (1-12) |
|
||||
| `priorityPlatforms` | array | Body | ❌ | Приоритетные платформы для продвижения |
|
||||
|
||||
#### Заголовки запроса
|
||||
|
||||
```
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
#### Тело запроса (JSON, опционально)
|
||||
|
||||
| Поле | Тип | Обязательный | Описание | Пример значения |
|
||||
| ------------------- | ------- | ------------ | --------------------------------------- | ------------------------------------- |
|
||||
| `durationWeeks` | integer | ❌ | Длительность стратегии в неделях (1-12) | 4 |
|
||||
| `priorityPlatforms` | array | ❌ | Список приоритетных платформ | ["Instagram", "LinkedIn", "Telegram"] |
|
||||
|
||||
#### Валидация полей
|
||||
|
||||
**`durationWeeks`** (integer, опциональное)
|
||||
|
||||
- Минимальное значение: 1
|
||||
- Максимальное значение: 12
|
||||
- По умолчанию: 4 (если не указано)
|
||||
|
||||
**`priorityPlatforms`** (array, опциональное)
|
||||
|
||||
- Допустимые платформы: `Instagram`, `Facebook`, `LinkedIn`, `Telegram`, `TikTok`, `YouTube`, `21MC` и другие
|
||||
- Если не указано, используются все популярные платформы
|
||||
|
||||
#### Пример запроса
|
||||
|
||||
```http
|
||||
POST /api/marketing/strategy/generate?analysisId=507f1f77bcf86cd799439011
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"durationWeeks": 4,
|
||||
"priorityPlatforms": ["Instagram", "LinkedIn", "Telegram"]
|
||||
}
|
||||
```
|
||||
|
||||
Или без тела запроса (используются значения по умолчанию):
|
||||
|
||||
```http
|
||||
POST /api/marketing/strategy/generate?analysisId=507f1f77bcf86cd799439011
|
||||
```
|
||||
|
||||
#### Пример успешного ответа (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Генерация стратегии запущена успешно. Результаты будут готовы в течение 3-5 минут.",
|
||||
"data": {
|
||||
"strategyId": "507f1f77bcf86cd799439012",
|
||||
"analysisId": "507f1f77bcf86cd799439011",
|
||||
"status": "queued",
|
||||
"createdAt": "2025-01-20T15:40:00",
|
||||
"durationWeeks": 4,
|
||||
"priorityPlatforms": ["Instagram", "LinkedIn", "Telegram"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Структура ответа
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
| ------------------------ | ------- | ----------------------------------------------------------------------- |
|
||||
| `success` | boolean | Флаг успешности операции |
|
||||
| `message` | string | Сообщение о результате операции |
|
||||
| `data.strategyId` | string | Уникальный идентификатор стратегии (MongoDB ObjectId) |
|
||||
| `data.analysisId` | string | ID маркетингового анализа |
|
||||
| `data.status` | string | Статус стратегии: `"queued"`, `"processing"`, `"completed"`, `"failed"` |
|
||||
| `data.createdAt` | string | ISO 8601 дата/время создания |
|
||||
| `data.durationWeeks` | integer | Длительность стратегии в неделях |
|
||||
| `data.priorityPlatforms` | array | Список приоритетных платформ |
|
||||
|
||||
#### Пример ошибки (404 Not Found - анализ не найден)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Анализ не найден",
|
||||
"error": {
|
||||
"code": "INVALID_ANALYSIS",
|
||||
"message": "Анализ с ID 507f1f77bcf86cd799439011 не найден"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Пример ошибки (400 Bad Request - анализ не завершен)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Анализ еще не завершен",
|
||||
"error": {
|
||||
"code": "ANALYSIS_NOT_COMPLETED",
|
||||
"message": "Анализ еще не завершен. Статус: processing"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Пример ошибки валидации (400 Bad Request)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Ошибка валидации",
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Ошибка валидации входных данных",
|
||||
"details": {
|
||||
"durationWeeks": "Длительность стратегии должна быть не менее 1 недели"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Получение стратегии по ID
|
||||
|
||||
**GET** `/api/marketing/strategy/{strategyId}`
|
||||
|
||||
Возвращает статус и результаты стратегии по идентификатору.
|
||||
|
||||
#### Параметры пути
|
||||
|
||||
| Параметр | Тип | Описание |
|
||||
| ------------ | ------ | ----------------------- |
|
||||
| `strategyId` | string | Идентификатор стратегии |
|
||||
|
||||
#### Пример запроса
|
||||
|
||||
```
|
||||
GET /api/marketing/strategy/507f1f77bcf86cd799439012
|
||||
```
|
||||
|
||||
#### Пример ответа (когда стратегия завершена - 200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Операция выполнена успешно",
|
||||
"data": {
|
||||
"strategyId": "507f1f77bcf86cd799439012",
|
||||
"analysisId": "507f1f77bcf86cd799439011",
|
||||
"status": "completed",
|
||||
"createdAt": "2025-01-20T15:40:00",
|
||||
"completedAt": "2025-01-20T15:43:00",
|
||||
"durationWeeks": 4,
|
||||
"priorityPlatforms": ["Instagram", "LinkedIn", "Telegram"],
|
||||
"strategy": {
|
||||
"weeklyPlans": [
|
||||
{
|
||||
"weekNumber": 1,
|
||||
"mainThemes": [
|
||||
"Презентация продукта",
|
||||
"Ключевые преимущества",
|
||||
"Решение проблем клиентов"
|
||||
],
|
||||
"contentRecommendations": "Сфокусируйтесь на представлении продукта и его основных преимуществах. Используйте визуальный контент для привлечения внимания. Подчеркните уникальные особенности, которые выделяют ваш продукт на рынке.",
|
||||
"priorityPlatforms": ["Instagram", "LinkedIn"]
|
||||
},
|
||||
{
|
||||
"weekNumber": 2,
|
||||
"mainThemes": [
|
||||
"Кейсы успешных клиентов",
|
||||
"Отзывы и рекомендации",
|
||||
"Демонстрация результатов"
|
||||
],
|
||||
"contentRecommendations": "Публикуйте реальные истории успеха ваших клиентов. Используйте отзывы и рекомендации для повышения доверия. Покажите конкретные результаты и достижения.",
|
||||
"priorityPlatforms": ["Instagram", "Telegram"]
|
||||
},
|
||||
{
|
||||
"weekNumber": 3,
|
||||
"mainThemes": [
|
||||
"Образовательный контент",
|
||||
"Советы и рекомендации",
|
||||
"Индустриальные инсайты"
|
||||
],
|
||||
"contentRecommendations": "Создавайте образовательный контент, который помогает вашей целевой аудитории. Делитесь экспертными знаниями и инсайтами индустрии. Позиционируйте себя как эксперта в области.",
|
||||
"priorityPlatforms": ["LinkedIn", "Telegram"]
|
||||
},
|
||||
{
|
||||
"weekNumber": 4,
|
||||
"mainThemes": [
|
||||
"Призыв к действию",
|
||||
"Специальные предложения",
|
||||
"Завершение кампании"
|
||||
],
|
||||
"contentRecommendations": "Активно призывайте к действию. Предлагайте специальные условия или бонусы. Подводите итоги кампании и демонстрируйте достигнутые результаты.",
|
||||
"priorityPlatforms": ["Instagram", "LinkedIn", "Telegram"]
|
||||
}
|
||||
],
|
||||
"postCalendar": [
|
||||
{
|
||||
"publishDate": "2025-01-21T10:00:00",
|
||||
"platform": "Instagram",
|
||||
"contentType": "пост",
|
||||
"theme": "Презентация продукта",
|
||||
"postText": "🚀 Представляем наш новый продукт! Мы создали решение, которое поможет вашему бизнесу достичь новых высот. Узнайте больше о ключевых преимуществах в нашем профиле. #бизнес #инновации #продукт",
|
||||
"hashtags": [
|
||||
"#бизнес",
|
||||
"#инновации",
|
||||
"#продукт",
|
||||
"#маркетинг",
|
||||
"#развитие"
|
||||
],
|
||||
"publishTime": "10:00"
|
||||
},
|
||||
{
|
||||
"publishDate": "2025-01-21T14:00:00",
|
||||
"platform": "LinkedIn",
|
||||
"contentType": "пост",
|
||||
"theme": "Ключевые преимущества",
|
||||
"postText": "Наш продукт предлагает уникальные преимущества для B2B клиентов: быстрая интеграция, масштабируемость и надежная поддержка. Свяжитесь с нами для консультации. #B2B #технологии #бизнес",
|
||||
"hashtags": [
|
||||
"#B2B",
|
||||
"#технологии",
|
||||
"#бизнес",
|
||||
"#решения",
|
||||
"#консультация"
|
||||
],
|
||||
"publishTime": "14:00"
|
||||
},
|
||||
{
|
||||
"publishDate": "2025-01-22T18:00:00",
|
||||
"platform": "Instagram",
|
||||
"contentType": "сторис",
|
||||
"theme": "Решение проблем клиентов",
|
||||
"postText": "Знаете ли вы, что 80% компаний сталкиваются с проблемой X? Наш продукт решает эту проблему эффективно и быстро. Swipe up для деталей! 👆",
|
||||
"hashtags": ["#решение", "#проблемы", "#эффективность"],
|
||||
"publishTime": "18:00"
|
||||
},
|
||||
{
|
||||
"publishDate": "2025-01-23T10:00:00",
|
||||
"platform": "Telegram",
|
||||
"contentType": "пост",
|
||||
"theme": "Кейс успешного клиента",
|
||||
"postText": "📊 Кейс: Как компания X увеличила эффективность на 150% с помощью нашего продукта. Читайте полную историю в нашем канале. #кейс #успех #результаты",
|
||||
"hashtags": ["#кейс", "#успех", "#результаты", "#бизнес"],
|
||||
"publishTime": "10:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Пример ответа (когда стратегия еще обрабатывается - 200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Операция выполнена успешно",
|
||||
"data": {
|
||||
"strategyId": "507f1f77bcf86cd799439012",
|
||||
"analysisId": "507f1f77bcf86cd799439011",
|
||||
"status": "processing",
|
||||
"createdAt": "2025-01-20T15:40:00",
|
||||
"completedAt": null,
|
||||
"durationWeeks": 4,
|
||||
"priorityPlatforms": ["Instagram", "LinkedIn", "Telegram"],
|
||||
"strategy": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Статусы стратегии
|
||||
|
||||
| Статус | Описание |
|
||||
| ------------ | ------------------------------- |
|
||||
| `queued` | Запрос в очереди на обработку |
|
||||
| `processing` | Стратегия генерируется |
|
||||
| `completed` | Стратегия завершена успешно |
|
||||
| `failed` | Стратегия завершилась с ошибкой |
|
||||
|
||||
#### Структура ответа
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
| ---------------------------------------------------- | ------- | ------------------------------------------------------ |
|
||||
| `success` | boolean | Флаг успешности операции |
|
||||
| `message` | string | Сообщение о результате операции |
|
||||
| `data.strategyId` | string | Уникальный идентификатор стратегии |
|
||||
| `data.analysisId` | string | ID маркетингового анализа |
|
||||
| `data.status` | string | Статус стратегии |
|
||||
| `data.createdAt` | string | ISO 8601 дата/время создания |
|
||||
| `data.completedAt` | string | ISO 8601 дата/время завершения (null если не завершен) |
|
||||
| `data.durationWeeks` | integer | Длительность стратегии в неделях |
|
||||
| `data.priorityPlatforms` | array | Список приоритетных платформ |
|
||||
| `data.strategy` | object | Объект со стратегией (null если не завершен) |
|
||||
| `data.strategy.weeklyPlans` | array | Список недельных планов |
|
||||
| `data.strategy.weeklyPlans[].weekNumber` | integer | Номер недели (1, 2, 3, ...) |
|
||||
| `data.strategy.weeklyPlans[].mainThemes` | array | Основные темы недели (массив строк) |
|
||||
| `data.strategy.weeklyPlans[].contentRecommendations` | string | Рекомендации по контенту для недели |
|
||||
| `data.strategy.weeklyPlans[].priorityPlatforms` | array | Приоритетные платформы для недели |
|
||||
| `data.strategy.postCalendar` | array | Календарь постов |
|
||||
| `data.strategy.postCalendar[].publishDate` | string | ISO 8601 дата/время публикации |
|
||||
| `data.strategy.postCalendar[].platform` | string | Платформа для публикации |
|
||||
| `data.strategy.postCalendar[].contentType` | string | Тип контента (пост, сторис, видео, баннер) |
|
||||
| `data.strategy.postCalendar[].theme` | string | Тема поста |
|
||||
| `data.strategy.postCalendar[].postText` | string | Полный текст поста (готовый к публикации) |
|
||||
| `data.strategy.postCalendar[].hashtags` | array | Список хештегов (массив строк) |
|
||||
| `data.strategy.postCalendar[].publishTime` | string | Время публикации в формате HH:mm |
|
||||
|
||||
#### Пример ошибки (404 Not Found)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Стратегия не найдена",
|
||||
"error": {
|
||||
"code": "NOT_FOUND",
|
||||
"message": "Стратегия с указанным ID не найдена"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Получение стратегии по ID анализа
|
||||
|
||||
**GET** `/api/marketing/analysis/{analysisId}/strategy`
|
||||
|
||||
Возвращает стратегию, связанную с указанным маркетинговым анализом.
|
||||
|
||||
#### Параметры пути
|
||||
|
||||
| Параметр | Тип | Описание |
|
||||
| ------------ | ------ | --------------------- |
|
||||
| `analysisId` | string | Идентификатор анализа |
|
||||
|
||||
#### Пример запроса
|
||||
|
||||
```
|
||||
GET /api/marketing/analysis/507f1f77bcf86cd799439011/strategy
|
||||
```
|
||||
|
||||
#### Пример ответа
|
||||
|
||||
Структура ответа идентична эндпоинту `GET /api/marketing/strategy/{strategyId}` (см. выше).
|
||||
|
||||
#### Пример ошибки (404 Not Found)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Стратегия не найдена",
|
||||
"error": {
|
||||
"code": "NOT_FOUND",
|
||||
"message": "Стратегия для указанного анализа не найдена"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Обработка ошибок
|
||||
|
||||
### Коды ошибок
|
||||
|
||||
| Код | HTTP статус | Описание |
|
||||
| ------------------------ | ----------- | ------------------------------- |
|
||||
| `VALIDATION_ERROR` | 400 | Ошибка валидации входных данных |
|
||||
| `INVALID_ANALYSIS` | 404 | Анализ не найден |
|
||||
| `ANALYSIS_NOT_COMPLETED` | 400 | Анализ еще не завершен |
|
||||
| `NOT_FOUND` | 404 | Стратегия не найдена |
|
||||
| `INTERNAL_SERVER_ERROR` | 500 | Внутренняя ошибка сервера |
|
||||
|
||||
### Формат ошибки
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Описание ошибки",
|
||||
"error": {
|
||||
"code": "ERROR_CODE",
|
||||
"message": "Детальное сообщение об ошибке",
|
||||
"details": {
|
||||
"field1": "Сообщение об ошибке для поля 1",
|
||||
"field2": "Сообщение об ошибке для поля 2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Примечание**: Поле `details` присутствует только для ошибок валидации (`VALIDATION_ERROR`).
|
||||
|
||||
---
|
||||
|
||||
## Примеры использования
|
||||
|
||||
### JavaScript/TypeScript (Fetch API)
|
||||
|
||||
#### Запуск генерации стратегии
|
||||
|
||||
```javascript
|
||||
async function generateStrategy(analysisId, options = {}) {
|
||||
const params = new URLSearchParams();
|
||||
params.append('analysisId', analysisId);
|
||||
|
||||
const response = await fetch(
|
||||
`https://api.konturai.kz/api/marketing/strategy/generate?${params}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
durationWeeks: options.durationWeeks || 4,
|
||||
priorityPlatforms: options.priorityPlatforms || [],
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
console.log('Strategy ID:', result.data.strategyId);
|
||||
return result.data.strategyId;
|
||||
} else {
|
||||
console.error('Error:', result.error);
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Проверка статуса и получение результата
|
||||
|
||||
```javascript
|
||||
async function getStrategyResult(strategyId) {
|
||||
const response = await fetch(
|
||||
`https://api.konturai.kz/api/marketing/strategy/${strategyId}`
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
const { status, strategy } = result.data;
|
||||
|
||||
if (status === 'completed' && strategy) {
|
||||
console.log('Strategy completed!');
|
||||
console.log('Weekly plans:', strategy.weeklyPlans);
|
||||
console.log('Post calendar:', strategy.postCalendar);
|
||||
return strategy;
|
||||
} else if (status === 'processing') {
|
||||
console.log('Strategy is still generating...');
|
||||
return null; // Повторить запрос позже
|
||||
} else if (status === 'failed') {
|
||||
throw new Error('Strategy generation failed');
|
||||
}
|
||||
} else {
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Получение стратегии по ID анализа
|
||||
|
||||
```javascript
|
||||
async function getStrategyByAnalysis(analysisId) {
|
||||
const response = await fetch(
|
||||
`https://api.konturai.kz/api/marketing/analysis/${analysisId}/strategy`
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
return result.data;
|
||||
} else {
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Полный цикл с polling
|
||||
|
||||
```javascript
|
||||
async function waitForStrategyCompletion(
|
||||
strategyId,
|
||||
maxAttempts = 60,
|
||||
intervalMs = 10000
|
||||
) {
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
const result = await getStrategyResult(strategyId);
|
||||
|
||||
if (result) {
|
||||
return result; // Стратегия завершена
|
||||
}
|
||||
|
||||
// Ждем перед следующей проверкой
|
||||
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
||||
}
|
||||
|
||||
throw new Error('Strategy generation timeout');
|
||||
}
|
||||
|
||||
// Использование
|
||||
async function runFullStrategyGeneration() {
|
||||
try {
|
||||
// 1. Получаем завершенный анализ (предполагается, что analysisId уже есть)
|
||||
const analysisId = '507f1f77bcf86cd799439011';
|
||||
|
||||
// 2. Запускаем генерацию стратегии
|
||||
const strategyId = await generateStrategy(analysisId, {
|
||||
durationWeeks: 4,
|
||||
priorityPlatforms: ['Instagram', 'LinkedIn', 'Telegram'],
|
||||
});
|
||||
|
||||
console.log(`Strategy generation started: ${strategyId}`);
|
||||
|
||||
// 3. Ждем завершения (проверяем каждые 10 секунд, максимум 10 минут)
|
||||
const strategy = await waitForStrategyCompletion(strategyId, 60, 10000);
|
||||
|
||||
// 4. Используем результаты
|
||||
console.log('Weekly plans:', strategy.weeklyPlans);
|
||||
console.log('Post calendar:', strategy.postCalendar);
|
||||
|
||||
// Отображаем календарь постов
|
||||
strategy.postCalendar.forEach((post) => {
|
||||
console.log(`${post.publishDate} - ${post.platform}: ${post.theme}`);
|
||||
});
|
||||
|
||||
return strategy;
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### React примеры
|
||||
|
||||
#### Компонент для отображения стратегии
|
||||
|
||||
```javascript
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
function StrategyView({ analysisId }) {
|
||||
const [strategy, setStrategy] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadStrategy() {
|
||||
try {
|
||||
// Сначала пытаемся получить существующую стратегию
|
||||
let response = await fetch(
|
||||
`/api/marketing/analysis/${analysisId}/strategy`
|
||||
);
|
||||
let result = await response.json();
|
||||
|
||||
if (result.success && result.data.status === 'completed') {
|
||||
setStrategy(result.data);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Если стратегии нет или она еще обрабатывается, запускаем генерацию
|
||||
if (!result.success || result.data.status === 'processing') {
|
||||
// Запускаем генерацию
|
||||
response = await fetch(
|
||||
`/api/marketing/strategy/generate?analysisId=${analysisId}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
durationWeeks: 4,
|
||||
priorityPlatforms: ['Instagram', 'LinkedIn', 'Telegram'],
|
||||
}),
|
||||
}
|
||||
);
|
||||
result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Polling для получения результата
|
||||
pollStrategy(result.data.strategyId);
|
||||
} else {
|
||||
setError(result.error.message);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function pollStrategy(strategyId) {
|
||||
const maxAttempts = 60;
|
||||
let attempts = 0;
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
attempts++;
|
||||
try {
|
||||
const response = await fetch(`/api/marketing/strategy/${strategyId}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
if (result.data.status === 'completed') {
|
||||
setStrategy(result.data);
|
||||
setLoading(false);
|
||||
clearInterval(interval);
|
||||
} else if (result.data.status === 'failed') {
|
||||
setError('Strategy generation failed');
|
||||
setLoading(false);
|
||||
clearInterval(interval);
|
||||
}
|
||||
}
|
||||
|
||||
if (attempts >= maxAttempts) {
|
||||
setError('Strategy generation timeout');
|
||||
setLoading(false);
|
||||
clearInterval(interval);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
setLoading(false);
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 10000); // Проверяем каждые 10 секунд
|
||||
}
|
||||
|
||||
if (analysisId) {
|
||||
loadStrategy();
|
||||
}
|
||||
}, [analysisId]);
|
||||
|
||||
if (loading) {
|
||||
return <div>Генерация стратегии...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div>Ошибка: {error}</div>;
|
||||
}
|
||||
|
||||
if (!strategy || !strategy.strategy) {
|
||||
return <div>Стратегия не найдена</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='strategy-view'>
|
||||
<h2>Стратегия продвижения</h2>
|
||||
|
||||
{/* Недельный план */}
|
||||
<section className='weekly-plans'>
|
||||
<h3>Недельный план</h3>
|
||||
{strategy.strategy.weeklyPlans.map((plan) => (
|
||||
<div key={plan.weekNumber} className='week-plan'>
|
||||
<h4>Неделя {plan.weekNumber}</h4>
|
||||
<div className='themes'>
|
||||
<strong>Темы:</strong>
|
||||
<ul>
|
||||
{plan.mainThemes.map((theme, idx) => (
|
||||
<li key={idx}>{theme}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className='recommendations'>
|
||||
<strong>Рекомендации:</strong>
|
||||
<p>{plan.contentRecommendations}</p>
|
||||
</div>
|
||||
<div className='platforms'>
|
||||
<strong>Платформы:</strong>
|
||||
{plan.priorityPlatforms.join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
|
||||
{/* Календарь постов */}
|
||||
<section className='post-calendar'>
|
||||
<h3>Календарь постов</h3>
|
||||
<div className='calendar-grid'>
|
||||
{strategy.strategy.postCalendar.map((post, idx) => (
|
||||
<div key={idx} className='post-item'>
|
||||
<div className='post-header'>
|
||||
<span className='date'>
|
||||
{new Date(post.publishDate).toLocaleDateString('ru-RU')}
|
||||
</span>
|
||||
<span className='time'>{post.publishTime}</span>
|
||||
<span className='platform'>{post.platform}</span>
|
||||
<span className='content-type'>{post.contentType}</span>
|
||||
</div>
|
||||
<div className='post-theme'>
|
||||
<strong>Тема:</strong> {post.theme}
|
||||
</div>
|
||||
<div className='post-text'>{post.postText}</div>
|
||||
<div className='post-hashtags'>
|
||||
{post.hashtags.map((tag, tagIdx) => (
|
||||
<span key={tagIdx} className='hashtag'>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default StrategyView;
|
||||
```
|
||||
|
||||
#### Компонент для отображения календаря постов
|
||||
|
||||
```javascript
|
||||
import React from 'react';
|
||||
|
||||
function PostCalendar({ postCalendar }) {
|
||||
// Группируем посты по датам
|
||||
const postsByDate = postCalendar.reduce((acc, post) => {
|
||||
const date = new Date(post.publishDate).toLocaleDateString('ru-RU');
|
||||
if (!acc[date]) {
|
||||
acc[date] = [];
|
||||
}
|
||||
acc[date].push(post);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return (
|
||||
<div className='post-calendar'>
|
||||
<h3>Календарь публикаций</h3>
|
||||
{Object.entries(postsByDate).map(([date, posts]) => (
|
||||
<div key={date} className='date-group'>
|
||||
<h4>{date}</h4>
|
||||
{posts.map((post, idx) => (
|
||||
<div key={idx} className='post-card'>
|
||||
<div className='post-meta'>
|
||||
<span className='platform-badge'>{post.platform}</span>
|
||||
<span className='content-type-badge'>{post.contentType}</span>
|
||||
<span className='time'>{post.publishTime}</span>
|
||||
</div>
|
||||
<div className='post-content'>
|
||||
<h5>{post.theme}</h5>
|
||||
<p>{post.postText}</p>
|
||||
<div className='hashtags'>
|
||||
{post.hashtags.map((tag, tagIdx) => (
|
||||
<span key={tagIdx} className='hashtag'>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PostCalendar;
|
||||
```
|
||||
|
||||
### Vue.js примеры
|
||||
|
||||
```javascript
|
||||
// composable для работы со стратегией
|
||||
export function useMarketingStrategy() {
|
||||
const baseUrl = '';
|
||||
|
||||
const generateStrategy = async (analysisId, options = {}) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('analysisId', analysisId);
|
||||
|
||||
const response = await fetch(
|
||||
`${baseUrl}/api/marketing/strategy/generate?${params}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
durationWeeks: options.durationWeeks || 4,
|
||||
priorityPlatforms: options.priorityPlatforms || [],
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getStrategy = async (strategyId) => {
|
||||
const response = await fetch(
|
||||
`${baseUrl}/api/marketing/strategy/${strategyId}`
|
||||
);
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const getStrategyByAnalysis = async (analysisId) => {
|
||||
const response = await fetch(
|
||||
`${baseUrl}/api/marketing/analysis/${analysisId}/strategy`
|
||||
);
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
return result.data;
|
||||
};
|
||||
|
||||
return {
|
||||
generateStrategy,
|
||||
getStrategy,
|
||||
getStrategyByAnalysis,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Рекомендации по интеграции
|
||||
|
||||
### 1. Polling стратегия
|
||||
|
||||
Рекомендуется проверять статус стратегии каждые 10-15 секунд. Максимальное время ожидания - 5-7 минут.
|
||||
|
||||
### 2. Обработка ошибок
|
||||
|
||||
Всегда проверяйте поле `success` в ответе и обрабатывайте ошибки соответствующим образом. Особое внимание уделите случаям, когда анализ еще не завершен.
|
||||
|
||||
### 3. Валидация на клиенте
|
||||
|
||||
Перед отправкой запроса рекомендуется валидировать данные на клиенте:
|
||||
|
||||
- Проверка наличия `analysisId`
|
||||
- Проверка диапазона `durationWeeks` (1-12)
|
||||
- Проверка формата массива `priorityPlatforms`
|
||||
|
||||
### 4. UX рекомендации
|
||||
|
||||
- Показывайте индикатор загрузки во время генерации стратегии
|
||||
- Отображайте примерное время завершения (3-5 минут)
|
||||
- Предоставьте возможность отменить ожидание и проверить результат позже
|
||||
- Сохраняйте `strategyId` для последующей проверки статуса
|
||||
- Отображайте календарь постов в удобном формате (календарь, список, таблица)
|
||||
- Позвольте пользователю копировать текст постов и хештеги
|
||||
|
||||
### 5. Кэширование
|
||||
|
||||
После получения результатов можно кэшировать их локально, используя `strategyId` или `analysisId` как ключ.
|
||||
|
||||
### 6. Экспорт данных
|
||||
|
||||
Рассмотрите возможность экспорта стратегии в различных форматах:
|
||||
|
||||
- CSV для календаря постов
|
||||
- PDF для полной стратегии
|
||||
- iCal для импорта в календарные приложения
|
||||
|
||||
---
|
||||
|
||||
## Примечания
|
||||
|
||||
1. **Формат даты**: Все даты возвращаются в формате ISO 8601 без timezone (LocalDateTime)
|
||||
2. **Идентификаторы**: Используются MongoDB ObjectId (24 символа hex)
|
||||
3. **Асинхронность**: Генерация стратегии выполняется асинхронно, не блокируя запрос
|
||||
4. **Таймауты**: Рекомендуется устанавливать таймаут на запросы (минимум 30 секунд для запуска генерации)
|
||||
5. **Зависимость от анализа**: Стратегия может быть сгенерирована только для завершенного анализа
|
||||
6. **Повторная генерация**: Если стратегия уже существует для анализа, возвращается существующая стратегия
|
||||
7. **Платформы**: Поддерживаются все популярные платформы: Instagram, Facebook, LinkedIn, Telegram, TikTok, YouTube, 21MC и другие
|
||||
|
||||
---
|
||||
|
||||
## Поддержка
|
||||
|
||||
При возникновении проблем с API обращайтесь в техническую поддержку с указанием:
|
||||
|
||||
- `strategyId` (если есть)
|
||||
- `analysisId`
|
||||
- Время запроса
|
||||
- Описание проблемы
|
||||
- Код ошибки (если есть)
|
||||
@@ -2,7 +2,9 @@ package kz.konturai.parser.controller;
|
||||
|
||||
import kz.konturai.parser.dto.*;
|
||||
import kz.konturai.parser.model.MarketingAnalysis;
|
||||
import kz.konturai.parser.model.MarketingStrategy;
|
||||
import kz.konturai.parser.service.MarketingAnalysisService;
|
||||
import kz.konturai.parser.service.MarketingStrategyService;
|
||||
import kz.konturai.parser.service.MinIOService;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -22,10 +24,15 @@ import java.util.Optional;
|
||||
public class MarketingController {
|
||||
|
||||
private final MarketingAnalysisService marketingAnalysisService;
|
||||
private final MarketingStrategyService marketingStrategyService;
|
||||
private final MinIOService minIOService;
|
||||
|
||||
public MarketingController(MarketingAnalysisService marketingAnalysisService, MinIOService minIOService) {
|
||||
public MarketingController(
|
||||
MarketingAnalysisService marketingAnalysisService,
|
||||
MarketingStrategyService marketingStrategyService,
|
||||
MinIOService minIOService) {
|
||||
this.marketingAnalysisService = marketingAnalysisService;
|
||||
this.marketingStrategyService = marketingStrategyService;
|
||||
this.minIOService = minIOService;
|
||||
}
|
||||
|
||||
@@ -97,6 +104,76 @@ public class MarketingController {
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/strategy/generate")
|
||||
public ResponseEntity<?> generateStrategy(
|
||||
@RequestParam String analysisId,
|
||||
@Valid @RequestBody(required = false) MarketingStrategyRequest request) {
|
||||
|
||||
if (request == null) {
|
||||
request = new MarketingStrategyRequest();
|
||||
}
|
||||
|
||||
try {
|
||||
MarketingStrategy strategy = marketingStrategyService.generateStrategy(analysisId, request);
|
||||
|
||||
MarketingStrategyResponse response = new MarketingStrategyResponse();
|
||||
response.setStrategyId(strategy.getId());
|
||||
response.setAnalysisId(strategy.getAnalysisId());
|
||||
response.setStatus(strategy.getStatus());
|
||||
response.setCreatedAt(strategy.getCreatedAt());
|
||||
response.setDurationWeeks(strategy.getDurationWeeks());
|
||||
response.setPriorityPlatforms(strategy.getPriorityPlatforms());
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(
|
||||
"Генерация стратегии запущена успешно. Результаты будут готовы в течение 3-5 минут.",
|
||||
response));
|
||||
} catch (IllegalArgumentException e) {
|
||||
ErrorResponse error = new ErrorResponse("INVALID_ANALYSIS", e.getMessage());
|
||||
return ResponseEntity.status(404)
|
||||
.body(ApiResponse.error("Анализ не найден", error));
|
||||
} catch (IllegalStateException e) {
|
||||
ErrorResponse error = new ErrorResponse("ANALYSIS_NOT_COMPLETED", e.getMessage());
|
||||
return ResponseEntity.status(400)
|
||||
.body(ApiResponse.error("Анализ еще не завершен", error));
|
||||
} catch (Exception e) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"INTERNAL_SERVER_ERROR",
|
||||
"Произошла ошибка при запуске генерации стратегии");
|
||||
return ResponseEntity.status(500)
|
||||
.body(ApiResponse.error("Внутренняя ошибка сервера", error));
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/strategy/{strategyId}")
|
||||
public ResponseEntity<?> getStrategy(@PathVariable String strategyId) {
|
||||
MarketingStrategyResponse result = marketingStrategyService.getStrategyResult(strategyId);
|
||||
|
||||
if (result == null) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"NOT_FOUND",
|
||||
"Стратегия с указанным ID не найдена");
|
||||
return ResponseEntity.status(404)
|
||||
.body(ApiResponse.error("Стратегия не найдена", error));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(result));
|
||||
}
|
||||
|
||||
@GetMapping("/{analysisId}/strategy")
|
||||
public ResponseEntity<?> getStrategyByAnalysis(@PathVariable String analysisId) {
|
||||
MarketingStrategyResponse result = marketingStrategyService.getStrategyByAnalysisId(analysisId);
|
||||
|
||||
if (result == null) {
|
||||
ErrorResponse error = new ErrorResponse(
|
||||
"NOT_FOUND",
|
||||
"Стратегия для указанного анализа не найдена");
|
||||
return ResponseEntity.status(404)
|
||||
.body(ApiResponse.error("Стратегия не найдена", error));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(result));
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ApiResponse<ErrorResponse>> handleValidationException(
|
||||
MethodArgumentNotValidException ex) {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package kz.konturai.parser.dto;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import java.util.List;
|
||||
|
||||
public class MarketingStrategyRequest {
|
||||
|
||||
@Min(value = 1, message = "Длительность стратегии должна быть не менее 1 недели")
|
||||
@Max(value = 12, message = "Длительность стратегии должна быть не более 12 недель")
|
||||
private Integer durationWeeks;
|
||||
|
||||
private List<String> priorityPlatforms;
|
||||
|
||||
public MarketingStrategyRequest() {
|
||||
}
|
||||
|
||||
public MarketingStrategyRequest(Integer durationWeeks, List<String> priorityPlatforms) {
|
||||
this.durationWeeks = durationWeeks;
|
||||
this.priorityPlatforms = priorityPlatforms;
|
||||
}
|
||||
|
||||
public Integer getDurationWeeks() {
|
||||
return durationWeeks;
|
||||
}
|
||||
|
||||
public void setDurationWeeks(Integer durationWeeks) {
|
||||
this.durationWeeks = durationWeeks;
|
||||
}
|
||||
|
||||
public List<String> getPriorityPlatforms() {
|
||||
return priorityPlatforms;
|
||||
}
|
||||
|
||||
public void setPriorityPlatforms(List<String> priorityPlatforms) {
|
||||
this.priorityPlatforms = priorityPlatforms;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
package kz.konturai.parser.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public class MarketingStrategyResponse {
|
||||
private String strategyId;
|
||||
private String analysisId;
|
||||
private String status;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime completedAt;
|
||||
private Integer durationWeeks;
|
||||
private List<String> priorityPlatforms;
|
||||
private StrategyContent strategy;
|
||||
|
||||
public MarketingStrategyResponse() {
|
||||
}
|
||||
|
||||
public MarketingStrategyResponse(String strategyId, String analysisId, String status, LocalDateTime createdAt, LocalDateTime completedAt, Integer durationWeeks, List<String> priorityPlatforms, StrategyContent strategy) {
|
||||
this.strategyId = strategyId;
|
||||
this.analysisId = analysisId;
|
||||
this.status = status;
|
||||
this.createdAt = createdAt;
|
||||
this.completedAt = completedAt;
|
||||
this.durationWeeks = durationWeeks;
|
||||
this.priorityPlatforms = priorityPlatforms;
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public String getStrategyId() {
|
||||
return strategyId;
|
||||
}
|
||||
|
||||
public void setStrategyId(String strategyId) {
|
||||
this.strategyId = strategyId;
|
||||
}
|
||||
|
||||
public String getAnalysisId() {
|
||||
return analysisId;
|
||||
}
|
||||
|
||||
public void setAnalysisId(String analysisId) {
|
||||
this.analysisId = analysisId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getCompletedAt() {
|
||||
return completedAt;
|
||||
}
|
||||
|
||||
public void setCompletedAt(LocalDateTime completedAt) {
|
||||
this.completedAt = completedAt;
|
||||
}
|
||||
|
||||
public Integer getDurationWeeks() {
|
||||
return durationWeeks;
|
||||
}
|
||||
|
||||
public void setDurationWeeks(Integer durationWeeks) {
|
||||
this.durationWeeks = durationWeeks;
|
||||
}
|
||||
|
||||
public List<String> getPriorityPlatforms() {
|
||||
return priorityPlatforms;
|
||||
}
|
||||
|
||||
public void setPriorityPlatforms(List<String> priorityPlatforms) {
|
||||
this.priorityPlatforms = priorityPlatforms;
|
||||
}
|
||||
|
||||
public StrategyContent getStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
public void setStrategy(StrategyContent strategy) {
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public static class StrategyContent {
|
||||
private List<WeeklyPlan> weeklyPlans;
|
||||
private List<PostCalendarItem> postCalendar;
|
||||
|
||||
public StrategyContent() {
|
||||
}
|
||||
|
||||
public StrategyContent(List<WeeklyPlan> weeklyPlans, List<PostCalendarItem> postCalendar) {
|
||||
this.weeklyPlans = weeklyPlans;
|
||||
this.postCalendar = postCalendar;
|
||||
}
|
||||
|
||||
public List<WeeklyPlan> getWeeklyPlans() {
|
||||
return weeklyPlans;
|
||||
}
|
||||
|
||||
public void setWeeklyPlans(List<WeeklyPlan> weeklyPlans) {
|
||||
this.weeklyPlans = weeklyPlans;
|
||||
}
|
||||
|
||||
public List<PostCalendarItem> getPostCalendar() {
|
||||
return postCalendar;
|
||||
}
|
||||
|
||||
public void setPostCalendar(List<PostCalendarItem> postCalendar) {
|
||||
this.postCalendar = postCalendar;
|
||||
}
|
||||
}
|
||||
|
||||
public static class WeeklyPlan {
|
||||
private Integer weekNumber;
|
||||
private List<String> mainThemes;
|
||||
private String contentRecommendations;
|
||||
private List<String> priorityPlatforms;
|
||||
|
||||
public WeeklyPlan() {
|
||||
}
|
||||
|
||||
public WeeklyPlan(Integer weekNumber, List<String> mainThemes, String contentRecommendations, List<String> priorityPlatforms) {
|
||||
this.weekNumber = weekNumber;
|
||||
this.mainThemes = mainThemes;
|
||||
this.contentRecommendations = contentRecommendations;
|
||||
this.priorityPlatforms = priorityPlatforms;
|
||||
}
|
||||
|
||||
public Integer getWeekNumber() {
|
||||
return weekNumber;
|
||||
}
|
||||
|
||||
public void setWeekNumber(Integer weekNumber) {
|
||||
this.weekNumber = weekNumber;
|
||||
}
|
||||
|
||||
public List<String> getMainThemes() {
|
||||
return mainThemes;
|
||||
}
|
||||
|
||||
public void setMainThemes(List<String> mainThemes) {
|
||||
this.mainThemes = mainThemes;
|
||||
}
|
||||
|
||||
public String getContentRecommendations() {
|
||||
return contentRecommendations;
|
||||
}
|
||||
|
||||
public void setContentRecommendations(String contentRecommendations) {
|
||||
this.contentRecommendations = contentRecommendations;
|
||||
}
|
||||
|
||||
public List<String> getPriorityPlatforms() {
|
||||
return priorityPlatforms;
|
||||
}
|
||||
|
||||
public void setPriorityPlatforms(List<String> priorityPlatforms) {
|
||||
this.priorityPlatforms = priorityPlatforms;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PostCalendarItem {
|
||||
private LocalDateTime publishDate;
|
||||
private String platform;
|
||||
private String contentType;
|
||||
private String theme;
|
||||
private String postText;
|
||||
private List<String> hashtags;
|
||||
private String publishTime;
|
||||
|
||||
public PostCalendarItem() {
|
||||
}
|
||||
|
||||
public PostCalendarItem(LocalDateTime publishDate, String platform, String contentType, String theme, String postText, List<String> hashtags, String publishTime) {
|
||||
this.publishDate = publishDate;
|
||||
this.platform = platform;
|
||||
this.contentType = contentType;
|
||||
this.theme = theme;
|
||||
this.postText = postText;
|
||||
this.hashtags = hashtags;
|
||||
this.publishTime = publishTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getPublishDate() {
|
||||
return publishDate;
|
||||
}
|
||||
|
||||
public void setPublishDate(LocalDateTime publishDate) {
|
||||
this.publishDate = publishDate;
|
||||
}
|
||||
|
||||
public String getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
public void setPlatform(String platform) {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public void setContentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public String getTheme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public void setTheme(String theme) {
|
||||
this.theme = theme;
|
||||
}
|
||||
|
||||
public String getPostText() {
|
||||
return postText;
|
||||
}
|
||||
|
||||
public void setPostText(String postText) {
|
||||
this.postText = postText;
|
||||
}
|
||||
|
||||
public List<String> getHashtags() {
|
||||
return hashtags;
|
||||
}
|
||||
|
||||
public void setHashtags(List<String> hashtags) {
|
||||
this.hashtags = hashtags;
|
||||
}
|
||||
|
||||
public String getPublishTime() {
|
||||
return publishTime;
|
||||
}
|
||||
|
||||
public void setPublishTime(String publishTime) {
|
||||
this.publishTime = publishTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
package kz.konturai.parser.model;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Document(collection = "marketing_strategy")
|
||||
public class MarketingStrategy {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@Field("analysis_id")
|
||||
private String analysisId;
|
||||
|
||||
@Field("status")
|
||||
private String status; // queued, processing, completed, failed
|
||||
|
||||
@Field("duration_weeks")
|
||||
private Integer durationWeeks;
|
||||
|
||||
@Field("priority_platforms")
|
||||
private List<String> priorityPlatforms;
|
||||
|
||||
@Field("weekly_plans")
|
||||
private List<WeeklyPlan> weeklyPlans;
|
||||
|
||||
@Field("post_calendar")
|
||||
private List<PostCalendarItem> postCalendar;
|
||||
|
||||
@Field("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Field("completed_at")
|
||||
private LocalDateTime completedAt;
|
||||
|
||||
@Field("strategy_data")
|
||||
private Map<String, Object> strategyData; // JSON data with full strategy content
|
||||
|
||||
public MarketingStrategy() {
|
||||
this.createdAt = LocalDateTime.now();
|
||||
this.status = "queued";
|
||||
}
|
||||
|
||||
public MarketingStrategy(String analysisId) {
|
||||
this();
|
||||
this.analysisId = analysisId;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getAnalysisId() {
|
||||
return analysisId;
|
||||
}
|
||||
|
||||
public void setAnalysisId(String analysisId) {
|
||||
this.analysisId = analysisId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Integer getDurationWeeks() {
|
||||
return durationWeeks;
|
||||
}
|
||||
|
||||
public void setDurationWeeks(Integer durationWeeks) {
|
||||
this.durationWeeks = durationWeeks;
|
||||
}
|
||||
|
||||
public List<String> getPriorityPlatforms() {
|
||||
return priorityPlatforms;
|
||||
}
|
||||
|
||||
public void setPriorityPlatforms(List<String> priorityPlatforms) {
|
||||
this.priorityPlatforms = priorityPlatforms;
|
||||
}
|
||||
|
||||
public List<WeeklyPlan> getWeeklyPlans() {
|
||||
return weeklyPlans;
|
||||
}
|
||||
|
||||
public void setWeeklyPlans(List<WeeklyPlan> weeklyPlans) {
|
||||
this.weeklyPlans = weeklyPlans;
|
||||
}
|
||||
|
||||
public List<PostCalendarItem> getPostCalendar() {
|
||||
return postCalendar;
|
||||
}
|
||||
|
||||
public void setPostCalendar(List<PostCalendarItem> postCalendar) {
|
||||
this.postCalendar = postCalendar;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public LocalDateTime getCompletedAt() {
|
||||
return completedAt;
|
||||
}
|
||||
|
||||
public void setCompletedAt(LocalDateTime completedAt) {
|
||||
this.completedAt = completedAt;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStrategyData() {
|
||||
return strategyData;
|
||||
}
|
||||
|
||||
public void setStrategyData(Map<String, Object> strategyData) {
|
||||
this.strategyData = strategyData;
|
||||
}
|
||||
|
||||
public static class WeeklyPlan {
|
||||
@Field("week_number")
|
||||
private Integer weekNumber;
|
||||
|
||||
@Field("main_themes")
|
||||
private List<String> mainThemes;
|
||||
|
||||
@Field("content_recommendations")
|
||||
private String contentRecommendations;
|
||||
|
||||
@Field("priority_platforms")
|
||||
private List<String> priorityPlatforms;
|
||||
|
||||
public WeeklyPlan() {
|
||||
}
|
||||
|
||||
public WeeklyPlan(Integer weekNumber, List<String> mainThemes, String contentRecommendations, List<String> priorityPlatforms) {
|
||||
this.weekNumber = weekNumber;
|
||||
this.mainThemes = mainThemes;
|
||||
this.contentRecommendations = contentRecommendations;
|
||||
this.priorityPlatforms = priorityPlatforms;
|
||||
}
|
||||
|
||||
public Integer getWeekNumber() {
|
||||
return weekNumber;
|
||||
}
|
||||
|
||||
public void setWeekNumber(Integer weekNumber) {
|
||||
this.weekNumber = weekNumber;
|
||||
}
|
||||
|
||||
public List<String> getMainThemes() {
|
||||
return mainThemes;
|
||||
}
|
||||
|
||||
public void setMainThemes(List<String> mainThemes) {
|
||||
this.mainThemes = mainThemes;
|
||||
}
|
||||
|
||||
public String getContentRecommendations() {
|
||||
return contentRecommendations;
|
||||
}
|
||||
|
||||
public void setContentRecommendations(String contentRecommendations) {
|
||||
this.contentRecommendations = contentRecommendations;
|
||||
}
|
||||
|
||||
public List<String> getPriorityPlatforms() {
|
||||
return priorityPlatforms;
|
||||
}
|
||||
|
||||
public void setPriorityPlatforms(List<String> priorityPlatforms) {
|
||||
this.priorityPlatforms = priorityPlatforms;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PostCalendarItem {
|
||||
@Field("publish_date")
|
||||
private LocalDateTime publishDate;
|
||||
|
||||
@Field("platform")
|
||||
private String platform;
|
||||
|
||||
@Field("content_type")
|
||||
private String contentType; // пост, сторис, видео, баннер
|
||||
|
||||
@Field("theme")
|
||||
private String theme;
|
||||
|
||||
@Field("post_text")
|
||||
private String postText;
|
||||
|
||||
@Field("hashtags")
|
||||
private List<String> hashtags;
|
||||
|
||||
@Field("publish_time")
|
||||
private String publishTime; // время публикации в формате HH:mm
|
||||
|
||||
public PostCalendarItem() {
|
||||
}
|
||||
|
||||
public PostCalendarItem(LocalDateTime publishDate, String platform, String contentType, String theme, String postText, List<String> hashtags, String publishTime) {
|
||||
this.publishDate = publishDate;
|
||||
this.platform = platform;
|
||||
this.contentType = contentType;
|
||||
this.theme = theme;
|
||||
this.postText = postText;
|
||||
this.hashtags = hashtags;
|
||||
this.publishTime = publishTime;
|
||||
}
|
||||
|
||||
public LocalDateTime getPublishDate() {
|
||||
return publishDate;
|
||||
}
|
||||
|
||||
public void setPublishDate(LocalDateTime publishDate) {
|
||||
this.publishDate = publishDate;
|
||||
}
|
||||
|
||||
public String getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
public void setPlatform(String platform) {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public void setContentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public String getTheme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public void setTheme(String theme) {
|
||||
this.theme = theme;
|
||||
}
|
||||
|
||||
public String getPostText() {
|
||||
return postText;
|
||||
}
|
||||
|
||||
public void setPostText(String postText) {
|
||||
this.postText = postText;
|
||||
}
|
||||
|
||||
public List<String> getHashtags() {
|
||||
return hashtags;
|
||||
}
|
||||
|
||||
public void setHashtags(List<String> hashtags) {
|
||||
this.hashtags = hashtags;
|
||||
}
|
||||
|
||||
public String getPublishTime() {
|
||||
return publishTime;
|
||||
}
|
||||
|
||||
public void setPublishTime(String publishTime) {
|
||||
this.publishTime = publishTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package kz.konturai.parser.repository;
|
||||
|
||||
import kz.konturai.parser.model.MarketingStrategy;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface MarketingStrategyRepository extends MongoRepository<MarketingStrategy, String> {
|
||||
Optional<MarketingStrategy> findById(String id);
|
||||
Optional<MarketingStrategy> findByAnalysisId(String analysisId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,501 @@
|
||||
package kz.konturai.parser.service;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import kz.konturai.parser.dto.MarketingAnalysisResult;
|
||||
import kz.konturai.parser.dto.MarketingStrategyRequest;
|
||||
import kz.konturai.parser.dto.MarketingStrategyResponse;
|
||||
import kz.konturai.parser.model.MarketingStrategy;
|
||||
import kz.konturai.parser.repository.MarketingStrategyRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class MarketingStrategyService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MarketingStrategyService.class);
|
||||
|
||||
private final MarketingStrategyRepository repository;
|
||||
private final MarketingAnalysisService marketingAnalysisService;
|
||||
private final OpenAIAnalyticsService openAIAnalyticsService;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public MarketingStrategyService(
|
||||
MarketingStrategyRepository repository,
|
||||
MarketingAnalysisService marketingAnalysisService,
|
||||
OpenAIAnalyticsService openAIAnalyticsService) {
|
||||
this.repository = repository;
|
||||
this.marketingAnalysisService = marketingAnalysisService;
|
||||
this.openAIAnalyticsService = openAIAnalyticsService;
|
||||
}
|
||||
|
||||
public MarketingStrategy generateStrategy(String analysisId, MarketingStrategyRequest request) {
|
||||
// Check if analysis exists and is completed
|
||||
MarketingAnalysisResult analysisResult = marketingAnalysisService.getAnalysisResult(analysisId);
|
||||
if (analysisResult == null) {
|
||||
throw new IllegalArgumentException("Анализ с ID " + analysisId + " не найден");
|
||||
}
|
||||
if (!"completed".equals(analysisResult.getStatus())) {
|
||||
throw new IllegalStateException("Анализ еще не завершен. Статус: " + analysisResult.getStatus());
|
||||
}
|
||||
|
||||
// Check if strategy already exists
|
||||
Optional<MarketingStrategy> existingStrategy = repository.findByAnalysisId(analysisId);
|
||||
if (existingStrategy.isPresent()) {
|
||||
logger.info("Стратегия для анализа {} уже существует: {}", analysisId, existingStrategy.get().getId());
|
||||
return existingStrategy.get();
|
||||
}
|
||||
|
||||
// Create new strategy
|
||||
MarketingStrategy strategy = new MarketingStrategy(analysisId);
|
||||
strategy.setDurationWeeks(request.getDurationWeeks() != null ? request.getDurationWeeks() : 4);
|
||||
strategy.setPriorityPlatforms(request.getPriorityPlatforms());
|
||||
strategy.setStatus("queued");
|
||||
strategy = repository.save(strategy);
|
||||
|
||||
logger.info("Marketing strategy created with ID: {}", strategy.getId());
|
||||
|
||||
// Start async processing
|
||||
processStrategyGeneration(strategy.getId(), analysisId, analysisResult);
|
||||
|
||||
return strategy;
|
||||
}
|
||||
|
||||
@Async("reportGenerationExecutor")
|
||||
public void processStrategyGeneration(String strategyId, String analysisId, MarketingAnalysisResult analysisResult) {
|
||||
try {
|
||||
Optional<MarketingStrategy> optStrategy = repository.findById(strategyId);
|
||||
if (optStrategy.isEmpty()) {
|
||||
logger.error("Marketing strategy not found: {}", strategyId);
|
||||
return;
|
||||
}
|
||||
|
||||
MarketingStrategy strategy = optStrategy.get();
|
||||
strategy.setStatus("processing");
|
||||
repository.save(strategy);
|
||||
|
||||
logger.info("Starting marketing strategy generation for ID: {}", strategyId);
|
||||
|
||||
// Build context from analysis
|
||||
String context = buildContextFromAnalysis(analysisResult);
|
||||
|
||||
// Generate weekly plans
|
||||
List<MarketingStrategy.WeeklyPlan> weeklyPlans = generateWeeklyPlans(
|
||||
context, strategy.getDurationWeeks(), strategy.getPriorityPlatforms());
|
||||
|
||||
// Generate post calendar
|
||||
List<MarketingStrategy.PostCalendarItem> postCalendar = generatePostCalendar(
|
||||
context, strategy.getDurationWeeks(), strategy.getPriorityPlatforms(), weeklyPlans);
|
||||
|
||||
// Update strategy
|
||||
strategy.setStatus("completed");
|
||||
strategy.setCompletedAt(LocalDateTime.now());
|
||||
strategy.setWeeklyPlans(weeklyPlans);
|
||||
strategy.setPostCalendar(postCalendar);
|
||||
|
||||
// Store full strategy data as JSON
|
||||
Map<String, Object> strategyData = new HashMap<>();
|
||||
strategyData.put("weeklyPlans", weeklyPlans);
|
||||
strategyData.put("postCalendar", postCalendar);
|
||||
strategy.setStrategyData(strategyData);
|
||||
|
||||
repository.save(strategy);
|
||||
|
||||
logger.info("Marketing strategy generation completed successfully for ID: {}", strategyId);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error processing marketing strategy {}: {}", strategyId, e.getMessage(), e);
|
||||
try {
|
||||
Optional<MarketingStrategy> optStrategy = repository.findById(strategyId);
|
||||
if (optStrategy.isPresent()) {
|
||||
MarketingStrategy strategy = optStrategy.get();
|
||||
strategy.setStatus("failed");
|
||||
repository.save(strategy);
|
||||
}
|
||||
} catch (Exception saveError) {
|
||||
logger.error("Failed to update strategy status to failed: {}", saveError.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String buildContextFromAnalysis(MarketingAnalysisResult analysisResult) {
|
||||
StringBuilder context = new StringBuilder();
|
||||
|
||||
if (analysisResult.getReport() != null) {
|
||||
MarketingAnalysisResult.MarketingReport report = analysisResult.getReport();
|
||||
|
||||
if (report.getSummary() != null) {
|
||||
context.append("Резюме анализа: ").append(report.getSummary()).append("\n\n");
|
||||
}
|
||||
|
||||
if (report.getTargetAudience() != null) {
|
||||
MarketingAnalysisResult.TargetAudience audience = report.getTargetAudience();
|
||||
context.append("Целевая аудитория: ").append(audience.getDescription()).append("\n");
|
||||
if (audience.getChannels() != null && !audience.getChannels().isEmpty()) {
|
||||
context.append("Рекомендуемые каналы: ").append(String.join(", ", audience.getChannels())).append("\n");
|
||||
}
|
||||
context.append("\n");
|
||||
}
|
||||
|
||||
if (report.getRecommendations() != null && !report.getRecommendations().isEmpty()) {
|
||||
context.append("Рекомендации:\n");
|
||||
for (String rec : report.getRecommendations()) {
|
||||
context.append("- ").append(rec).append("\n");
|
||||
}
|
||||
context.append("\n");
|
||||
}
|
||||
|
||||
if (report.getStrategy() != null) {
|
||||
MarketingAnalysisResult.Strategy strategy = report.getStrategy();
|
||||
context.append("Базовая стратегия:\n");
|
||||
if (strategy.getDuration() != null) {
|
||||
context.append("Длительность: ").append(strategy.getDuration()).append("\n");
|
||||
}
|
||||
if (strategy.getChannels() != null && !strategy.getChannels().isEmpty()) {
|
||||
context.append("Каналы: ").append(String.join(", ", strategy.getChannels())).append("\n");
|
||||
}
|
||||
if (strategy.getContentTypes() != null && !strategy.getContentTypes().isEmpty()) {
|
||||
context.append("Типы контента: ").append(String.join(", ", strategy.getContentTypes())).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return context.toString();
|
||||
}
|
||||
|
||||
private List<MarketingStrategy.WeeklyPlan> generateWeeklyPlans(
|
||||
String context, Integer durationWeeks, List<String> priorityPlatforms) {
|
||||
try {
|
||||
String platformsStr = priorityPlatforms != null && !priorityPlatforms.isEmpty()
|
||||
? String.join(", ", priorityPlatforms)
|
||||
: "Instagram, Facebook, LinkedIn, Telegram, TikTok, YouTube";
|
||||
|
||||
String prompt = String.format(
|
||||
"На основе следующего маркетингового анализа создай детальный недельный план продвижения на %d недель.\n\n" +
|
||||
"Требования:\n" +
|
||||
"1. Создай план для каждой недели отдельно\n" +
|
||||
"2. Для каждой недели укажи:\n" +
|
||||
" - Основные темы недели (3-5 тем)\n" +
|
||||
" - Рекомендации по контенту (1-2 абзаца)\n" +
|
||||
" - Приоритетные платформы для этой недели\n" +
|
||||
"3. Платформы для использования: %s\n" +
|
||||
"4. Ответ должен быть структурированным и применимым на практике\n" +
|
||||
"5. Ответ должен быть на русском языке\n\n" +
|
||||
"Верни ответ в формате JSON со следующей структурой:\n" +
|
||||
"{\n" +
|
||||
" \"weeklyPlans\": [\n" +
|
||||
" {\n" +
|
||||
" \"weekNumber\": 1,\n" +
|
||||
" \"mainThemes\": [\"тема1\", \"тема2\", \"тема3\"],\n" +
|
||||
" \"contentRecommendations\": \"рекомендации по контенту\",\n" +
|
||||
" \"priorityPlatforms\": [\"Instagram\", \"Facebook\"]\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}\n\n" +
|
||||
"Маркетинговый анализ:\n%s",
|
||||
durationWeeks, platformsStr, context);
|
||||
|
||||
String response = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru");
|
||||
if (response == null || response.trim().isEmpty()) {
|
||||
logger.warn("Failed to generate weekly plans, using default");
|
||||
return generateDefaultWeeklyPlans(durationWeeks, priorityPlatforms);
|
||||
}
|
||||
|
||||
// Extract JSON from response
|
||||
String jsonStr = extractJsonFromResponse(response);
|
||||
if (jsonStr == null) {
|
||||
logger.warn("Could not extract JSON from weekly plans response, using default");
|
||||
return generateDefaultWeeklyPlans(durationWeeks, priorityPlatforms);
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
Map<String, Object> jsonMap = objectMapper.readValue(jsonStr, new TypeReference<Map<String, Object>>() {});
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> weeklyPlansList = (List<Map<String, Object>>) jsonMap.get("weeklyPlans");
|
||||
|
||||
if (weeklyPlansList == null || weeklyPlansList.isEmpty()) {
|
||||
logger.warn("Weekly plans list is empty, using default");
|
||||
return generateDefaultWeeklyPlans(durationWeeks, priorityPlatforms);
|
||||
}
|
||||
|
||||
List<MarketingStrategy.WeeklyPlan> plans = new ArrayList<>();
|
||||
for (Map<String, Object> planMap : weeklyPlansList) {
|
||||
MarketingStrategy.WeeklyPlan plan = new MarketingStrategy.WeeklyPlan();
|
||||
plan.setWeekNumber(((Number) planMap.get("weekNumber")).intValue());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> themes = (List<String>) planMap.get("mainThemes");
|
||||
plan.setMainThemes(themes != null ? themes : new ArrayList<>());
|
||||
|
||||
plan.setContentRecommendations((String) planMap.get("contentRecommendations"));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> platforms = (List<String>) planMap.get("priorityPlatforms");
|
||||
plan.setPriorityPlatforms(platforms != null ? platforms : new ArrayList<>());
|
||||
|
||||
plans.add(plan);
|
||||
}
|
||||
|
||||
return plans;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error generating weekly plans: {}", e.getMessage(), e);
|
||||
return generateDefaultWeeklyPlans(durationWeeks, priorityPlatforms);
|
||||
}
|
||||
}
|
||||
|
||||
private List<MarketingStrategy.PostCalendarItem> generatePostCalendar(
|
||||
String context, Integer durationWeeks, List<String> priorityPlatforms,
|
||||
List<MarketingStrategy.WeeklyPlan> weeklyPlans) {
|
||||
try {
|
||||
String platformsStr = priorityPlatforms != null && !priorityPlatforms.isEmpty()
|
||||
? String.join(", ", priorityPlatforms)
|
||||
: "Instagram, Facebook, LinkedIn, Telegram, TikTok, YouTube";
|
||||
|
||||
// Build weekly plans summary for context
|
||||
String weeklyPlansSummary = "";
|
||||
if (weeklyPlans != null && !weeklyPlans.isEmpty()) {
|
||||
weeklyPlansSummary = weeklyPlans.stream()
|
||||
.map(plan -> String.format("Неделя %d: %s", plan.getWeekNumber(),
|
||||
plan.getMainThemes() != null ? String.join(", ", plan.getMainThemes()) : ""))
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
LocalDateTime startDate = LocalDateTime.now().plusDays(1); // Start from tomorrow
|
||||
|
||||
String prompt = String.format(
|
||||
"На основе следующего маркетингового анализа и недельного плана создай детальный календарь постов на %d недель.\n\n" +
|
||||
"Требования:\n" +
|
||||
"1. Создай конкретные посты для публикации\n" +
|
||||
"2. Для каждого поста укажи:\n" +
|
||||
" - Дату публикации (в формате YYYY-MM-DD)\n" +
|
||||
" - Время публикации (в формате HH:mm, например 10:00, 14:00, 18:00)\n" +
|
||||
" - Платформу (одну из: %s)\n" +
|
||||
" - Тип контента (пост, сторис, видео, баннер)\n" +
|
||||
" - Тему поста\n" +
|
||||
" - Полный текст поста (готовый к публикации, 100-300 символов)\n" +
|
||||
" - Хештеги (5-10 релевантных хештегов)\n" +
|
||||
"3. Распредели посты равномерно по неделям\n" +
|
||||
"4. Рекомендуемое количество постов: 3-5 постов в неделю\n" +
|
||||
"5. Чередуй платформы и типы контента\n" +
|
||||
"6. Ответ должен быть на русском языке\n\n" +
|
||||
"Верни ответ в формате JSON со следующей структурой:\n" +
|
||||
"{\n" +
|
||||
" \"postCalendar\": [\n" +
|
||||
" {\n" +
|
||||
" \"publishDate\": \"2024-01-15T10:00:00\",\n" +
|
||||
" \"platform\": \"Instagram\",\n" +
|
||||
" \"contentType\": \"пост\",\n" +
|
||||
" \"theme\": \"тема поста\",\n" +
|
||||
" \"postText\": \"полный текст поста\",\n" +
|
||||
" \"hashtags\": [\"#хештег1\", \"#хештег2\"],\n" +
|
||||
" \"publishTime\": \"10:00\"\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}\n\n" +
|
||||
"Начальная дата: %s\n\n" +
|
||||
"Недельный план:\n%s\n\n" +
|
||||
"Маркетинговый анализ:\n%s",
|
||||
durationWeeks, platformsStr, startDate.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), weeklyPlansSummary, context);
|
||||
|
||||
String response = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru");
|
||||
if (response == null || response.trim().isEmpty()) {
|
||||
logger.warn("Failed to generate post calendar, using default");
|
||||
return generateDefaultPostCalendar(durationWeeks, priorityPlatforms, startDate);
|
||||
}
|
||||
|
||||
// Extract JSON from response
|
||||
String jsonStr = extractJsonFromResponse(response);
|
||||
if (jsonStr == null) {
|
||||
logger.warn("Could not extract JSON from post calendar response, using default");
|
||||
return generateDefaultPostCalendar(durationWeeks, priorityPlatforms, startDate);
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
Map<String, Object> jsonMap = objectMapper.readValue(jsonStr, new TypeReference<Map<String, Object>>() {});
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> postCalendarList = (List<Map<String, Object>>) jsonMap.get("postCalendar");
|
||||
|
||||
if (postCalendarList == null || postCalendarList.isEmpty()) {
|
||||
logger.warn("Post calendar list is empty, using default");
|
||||
return generateDefaultPostCalendar(durationWeeks, priorityPlatforms, startDate);
|
||||
}
|
||||
|
||||
List<MarketingStrategy.PostCalendarItem> calendar = new ArrayList<>();
|
||||
for (Map<String, Object> postMap : postCalendarList) {
|
||||
MarketingStrategy.PostCalendarItem item = new MarketingStrategy.PostCalendarItem();
|
||||
|
||||
// Parse date
|
||||
String dateStr = (String) postMap.get("publishDate");
|
||||
if (dateStr != null) {
|
||||
try {
|
||||
LocalDateTime publishDate = LocalDateTime.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
item.setPublishDate(publishDate);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to parse date: {}", dateStr);
|
||||
item.setPublishDate(startDate.plusDays(calendar.size()));
|
||||
}
|
||||
} else {
|
||||
item.setPublishDate(startDate.plusDays(calendar.size()));
|
||||
}
|
||||
|
||||
item.setPlatform((String) postMap.get("platform"));
|
||||
item.setContentType((String) postMap.get("contentType"));
|
||||
item.setTheme((String) postMap.get("theme"));
|
||||
item.setPostText((String) postMap.get("postText"));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> hashtags = (List<String>) postMap.get("hashtags");
|
||||
item.setHashtags(hashtags != null ? hashtags : new ArrayList<>());
|
||||
|
||||
item.setPublishTime((String) postMap.get("publishTime"));
|
||||
|
||||
calendar.add(item);
|
||||
}
|
||||
|
||||
return calendar;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error generating post calendar: {}", e.getMessage(), e);
|
||||
return generateDefaultPostCalendar(durationWeeks, priorityPlatforms, LocalDateTime.now().plusDays(1));
|
||||
}
|
||||
}
|
||||
|
||||
private String extractJsonFromResponse(String response) {
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to find JSON object in response
|
||||
int startIdx = response.indexOf("{");
|
||||
int endIdx = response.lastIndexOf("}");
|
||||
|
||||
if (startIdx >= 0 && endIdx > startIdx) {
|
||||
return response.substring(startIdx, endIdx + 1);
|
||||
}
|
||||
|
||||
// Try to find JSON array
|
||||
startIdx = response.indexOf("[");
|
||||
endIdx = response.lastIndexOf("]");
|
||||
|
||||
if (startIdx >= 0 && endIdx > startIdx) {
|
||||
return "{\"data\":" + response.substring(startIdx, endIdx + 1) + "}";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<MarketingStrategy.WeeklyPlan> generateDefaultWeeklyPlans(
|
||||
Integer durationWeeks, List<String> priorityPlatforms) {
|
||||
List<MarketingStrategy.WeeklyPlan> plans = new ArrayList<>();
|
||||
List<String> platforms = priorityPlatforms != null && !priorityPlatforms.isEmpty()
|
||||
? priorityPlatforms
|
||||
: Arrays.asList("Instagram", "Facebook", "LinkedIn");
|
||||
|
||||
for (int i = 1; i <= durationWeeks; i++) {
|
||||
MarketingStrategy.WeeklyPlan plan = new MarketingStrategy.WeeklyPlan();
|
||||
plan.setWeekNumber(i);
|
||||
plan.setMainThemes(Arrays.asList("Презентация продукта", "Преимущества", "Отзывы клиентов"));
|
||||
plan.setContentRecommendations("Создавайте контент, который демонстрирует ценность продукта для целевой аудитории.");
|
||||
plan.setPriorityPlatforms(platforms);
|
||||
plans.add(plan);
|
||||
}
|
||||
|
||||
return plans;
|
||||
}
|
||||
|
||||
private List<MarketingStrategy.PostCalendarItem> generateDefaultPostCalendar(
|
||||
Integer durationWeeks, List<String> priorityPlatforms, LocalDateTime startDate) {
|
||||
List<MarketingStrategy.PostCalendarItem> calendar = new ArrayList<>();
|
||||
List<String> platforms = priorityPlatforms != null && !priorityPlatforms.isEmpty()
|
||||
? priorityPlatforms
|
||||
: Arrays.asList("Instagram", "Facebook", "LinkedIn");
|
||||
List<String> contentTypes = Arrays.asList("пост", "сторис", "видео");
|
||||
List<String> times = Arrays.asList("10:00", "14:00", "18:00");
|
||||
|
||||
int postCount = durationWeeks * 3; // 3 posts per week
|
||||
for (int i = 0; i < postCount; i++) {
|
||||
MarketingStrategy.PostCalendarItem item = new MarketingStrategy.PostCalendarItem();
|
||||
item.setPublishDate(startDate.plusDays(i * 2)); // Every 2 days
|
||||
item.setPlatform(platforms.get(i % platforms.size()));
|
||||
item.setContentType(contentTypes.get(i % contentTypes.size()));
|
||||
item.setTheme("Тема поста " + (i + 1));
|
||||
item.setPostText("Текст поста для публикации на платформе " + item.getPlatform());
|
||||
item.setHashtags(Arrays.asList("#маркетинг", "#бизнес", "#продвижение"));
|
||||
item.setPublishTime(times.get(i % times.size()));
|
||||
calendar.add(item);
|
||||
}
|
||||
|
||||
return calendar;
|
||||
}
|
||||
|
||||
public MarketingStrategyResponse getStrategyResult(String strategyId) {
|
||||
Optional<MarketingStrategy> optStrategy = repository.findById(strategyId);
|
||||
if (optStrategy.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MarketingStrategy strategy = optStrategy.get();
|
||||
MarketingStrategyResponse response = new MarketingStrategyResponse();
|
||||
response.setStrategyId(strategy.getId());
|
||||
response.setAnalysisId(strategy.getAnalysisId());
|
||||
response.setStatus(strategy.getStatus());
|
||||
response.setCreatedAt(strategy.getCreatedAt());
|
||||
response.setCompletedAt(strategy.getCompletedAt());
|
||||
response.setDurationWeeks(strategy.getDurationWeeks());
|
||||
response.setPriorityPlatforms(strategy.getPriorityPlatforms());
|
||||
|
||||
if (strategy.getWeeklyPlans() != null && strategy.getPostCalendar() != null
|
||||
&& "completed".equals(strategy.getStatus())) {
|
||||
MarketingStrategyResponse.StrategyContent strategyContent = new MarketingStrategyResponse.StrategyContent();
|
||||
|
||||
// Convert WeeklyPlans
|
||||
List<MarketingStrategyResponse.WeeklyPlan> weeklyPlans = new ArrayList<>();
|
||||
for (MarketingStrategy.WeeklyPlan plan : strategy.getWeeklyPlans()) {
|
||||
MarketingStrategyResponse.WeeklyPlan dtoPlan = new MarketingStrategyResponse.WeeklyPlan();
|
||||
dtoPlan.setWeekNumber(plan.getWeekNumber());
|
||||
dtoPlan.setMainThemes(plan.getMainThemes());
|
||||
dtoPlan.setContentRecommendations(plan.getContentRecommendations());
|
||||
dtoPlan.setPriorityPlatforms(plan.getPriorityPlatforms());
|
||||
weeklyPlans.add(dtoPlan);
|
||||
}
|
||||
strategyContent.setWeeklyPlans(weeklyPlans);
|
||||
|
||||
// Convert PostCalendarItems
|
||||
List<MarketingStrategyResponse.PostCalendarItem> postCalendar = new ArrayList<>();
|
||||
for (MarketingStrategy.PostCalendarItem item : strategy.getPostCalendar()) {
|
||||
MarketingStrategyResponse.PostCalendarItem dtoItem = new MarketingStrategyResponse.PostCalendarItem();
|
||||
dtoItem.setPublishDate(item.getPublishDate());
|
||||
dtoItem.setPlatform(item.getPlatform());
|
||||
dtoItem.setContentType(item.getContentType());
|
||||
dtoItem.setTheme(item.getTheme());
|
||||
dtoItem.setPostText(item.getPostText());
|
||||
dtoItem.setHashtags(item.getHashtags());
|
||||
dtoItem.setPublishTime(item.getPublishTime());
|
||||
postCalendar.add(dtoItem);
|
||||
}
|
||||
strategyContent.setPostCalendar(postCalendar);
|
||||
|
||||
response.setStrategy(strategyContent);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public MarketingStrategyResponse getStrategyByAnalysisId(String analysisId) {
|
||||
Optional<MarketingStrategy> optStrategy = repository.findByAnalysisId(analysisId);
|
||||
if (optStrategy.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getStrategyResult(optStrategy.get().getId());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user