diff --git a/marketing-strategy-api-frontend.md b/marketing-strategy-api-frontend.md
new file mode 100644
index 0000000..b16fbe7
--- /dev/null
+++ b/marketing-strategy-api-frontend.md
@@ -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
Генерация стратегии...
;
+ }
+
+ if (error) {
+ return Ошибка: {error}
;
+ }
+
+ if (!strategy || !strategy.strategy) {
+ return Стратегия не найдена
;
+ }
+
+ return (
+
+
Стратегия продвижения
+
+ {/* Недельный план */}
+
+ Недельный план
+ {strategy.strategy.weeklyPlans.map((plan) => (
+
+
Неделя {plan.weekNumber}
+
+
Темы:
+
+ {plan.mainThemes.map((theme, idx) => (
+ - {theme}
+ ))}
+
+
+
+
Рекомендации:
+
{plan.contentRecommendations}
+
+
+ Платформы:
+ {plan.priorityPlatforms.join(', ')}
+
+
+ ))}
+
+
+ {/* Календарь постов */}
+
+ Календарь постов
+
+ {strategy.strategy.postCalendar.map((post, idx) => (
+
+
+
+ {new Date(post.publishDate).toLocaleDateString('ru-RU')}
+
+ {post.publishTime}
+ {post.platform}
+ {post.contentType}
+
+
+ Тема: {post.theme}
+
+
{post.postText}
+
+ {post.hashtags.map((tag, tagIdx) => (
+
+ {tag}
+
+ ))}
+
+
+ ))}
+
+
+
+ );
+}
+
+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 (
+
+
Календарь публикаций
+ {Object.entries(postsByDate).map(([date, posts]) => (
+
+
{date}
+ {posts.map((post, idx) => (
+
+
+ {post.platform}
+ {post.contentType}
+ {post.publishTime}
+
+
+
{post.theme}
+
{post.postText}
+
+ {post.hashtags.map((tag, tagIdx) => (
+
+ {tag}
+
+ ))}
+
+
+
+ ))}
+
+ ))}
+
+ );
+}
+
+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`
+- Время запроса
+- Описание проблемы
+- Код ошибки (если есть)
diff --git a/src/main/java/kz/konturai/parser/controller/MarketingController.java b/src/main/java/kz/konturai/parser/controller/MarketingController.java
index fc5575d..4ddce17 100644
--- a/src/main/java/kz/konturai/parser/controller/MarketingController.java
+++ b/src/main/java/kz/konturai/parser/controller/MarketingController.java
@@ -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> handleValidationException(
MethodArgumentNotValidException ex) {
diff --git a/src/main/java/kz/konturai/parser/dto/MarketingStrategyRequest.java b/src/main/java/kz/konturai/parser/dto/MarketingStrategyRequest.java
new file mode 100644
index 0000000..8b8950f
--- /dev/null
+++ b/src/main/java/kz/konturai/parser/dto/MarketingStrategyRequest.java
@@ -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 priorityPlatforms;
+
+ public MarketingStrategyRequest() {
+ }
+
+ public MarketingStrategyRequest(Integer durationWeeks, List priorityPlatforms) {
+ this.durationWeeks = durationWeeks;
+ this.priorityPlatforms = priorityPlatforms;
+ }
+
+ public Integer getDurationWeeks() {
+ return durationWeeks;
+ }
+
+ public void setDurationWeeks(Integer durationWeeks) {
+ this.durationWeeks = durationWeeks;
+ }
+
+ public List getPriorityPlatforms() {
+ return priorityPlatforms;
+ }
+
+ public void setPriorityPlatforms(List priorityPlatforms) {
+ this.priorityPlatforms = priorityPlatforms;
+ }
+}
+
diff --git a/src/main/java/kz/konturai/parser/dto/MarketingStrategyResponse.java b/src/main/java/kz/konturai/parser/dto/MarketingStrategyResponse.java
new file mode 100644
index 0000000..1fef964
--- /dev/null
+++ b/src/main/java/kz/konturai/parser/dto/MarketingStrategyResponse.java
@@ -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 priorityPlatforms;
+ private StrategyContent strategy;
+
+ public MarketingStrategyResponse() {
+ }
+
+ public MarketingStrategyResponse(String strategyId, String analysisId, String status, LocalDateTime createdAt, LocalDateTime completedAt, Integer durationWeeks, List 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 getPriorityPlatforms() {
+ return priorityPlatforms;
+ }
+
+ public void setPriorityPlatforms(List priorityPlatforms) {
+ this.priorityPlatforms = priorityPlatforms;
+ }
+
+ public StrategyContent getStrategy() {
+ return strategy;
+ }
+
+ public void setStrategy(StrategyContent strategy) {
+ this.strategy = strategy;
+ }
+
+ public static class StrategyContent {
+ private List weeklyPlans;
+ private List postCalendar;
+
+ public StrategyContent() {
+ }
+
+ public StrategyContent(List weeklyPlans, List postCalendar) {
+ this.weeklyPlans = weeklyPlans;
+ this.postCalendar = postCalendar;
+ }
+
+ public List getWeeklyPlans() {
+ return weeklyPlans;
+ }
+
+ public void setWeeklyPlans(List weeklyPlans) {
+ this.weeklyPlans = weeklyPlans;
+ }
+
+ public List getPostCalendar() {
+ return postCalendar;
+ }
+
+ public void setPostCalendar(List postCalendar) {
+ this.postCalendar = postCalendar;
+ }
+ }
+
+ public static class WeeklyPlan {
+ private Integer weekNumber;
+ private List mainThemes;
+ private String contentRecommendations;
+ private List priorityPlatforms;
+
+ public WeeklyPlan() {
+ }
+
+ public WeeklyPlan(Integer weekNumber, List mainThemes, String contentRecommendations, List 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 getMainThemes() {
+ return mainThemes;
+ }
+
+ public void setMainThemes(List mainThemes) {
+ this.mainThemes = mainThemes;
+ }
+
+ public String getContentRecommendations() {
+ return contentRecommendations;
+ }
+
+ public void setContentRecommendations(String contentRecommendations) {
+ this.contentRecommendations = contentRecommendations;
+ }
+
+ public List getPriorityPlatforms() {
+ return priorityPlatforms;
+ }
+
+ public void setPriorityPlatforms(List 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 hashtags;
+ private String publishTime;
+
+ public PostCalendarItem() {
+ }
+
+ public PostCalendarItem(LocalDateTime publishDate, String platform, String contentType, String theme, String postText, List 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 getHashtags() {
+ return hashtags;
+ }
+
+ public void setHashtags(List hashtags) {
+ this.hashtags = hashtags;
+ }
+
+ public String getPublishTime() {
+ return publishTime;
+ }
+
+ public void setPublishTime(String publishTime) {
+ this.publishTime = publishTime;
+ }
+ }
+}
+
diff --git a/src/main/java/kz/konturai/parser/model/MarketingStrategy.java b/src/main/java/kz/konturai/parser/model/MarketingStrategy.java
new file mode 100644
index 0000000..b8809ec
--- /dev/null
+++ b/src/main/java/kz/konturai/parser/model/MarketingStrategy.java
@@ -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 priorityPlatforms;
+
+ @Field("weekly_plans")
+ private List weeklyPlans;
+
+ @Field("post_calendar")
+ private List postCalendar;
+
+ @Field("created_at")
+ private LocalDateTime createdAt;
+
+ @Field("completed_at")
+ private LocalDateTime completedAt;
+
+ @Field("strategy_data")
+ private Map 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 getPriorityPlatforms() {
+ return priorityPlatforms;
+ }
+
+ public void setPriorityPlatforms(List priorityPlatforms) {
+ this.priorityPlatforms = priorityPlatforms;
+ }
+
+ public List getWeeklyPlans() {
+ return weeklyPlans;
+ }
+
+ public void setWeeklyPlans(List weeklyPlans) {
+ this.weeklyPlans = weeklyPlans;
+ }
+
+ public List getPostCalendar() {
+ return postCalendar;
+ }
+
+ public void setPostCalendar(List 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 getStrategyData() {
+ return strategyData;
+ }
+
+ public void setStrategyData(Map strategyData) {
+ this.strategyData = strategyData;
+ }
+
+ public static class WeeklyPlan {
+ @Field("week_number")
+ private Integer weekNumber;
+
+ @Field("main_themes")
+ private List mainThemes;
+
+ @Field("content_recommendations")
+ private String contentRecommendations;
+
+ @Field("priority_platforms")
+ private List priorityPlatforms;
+
+ public WeeklyPlan() {
+ }
+
+ public WeeklyPlan(Integer weekNumber, List mainThemes, String contentRecommendations, List 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 getMainThemes() {
+ return mainThemes;
+ }
+
+ public void setMainThemes(List mainThemes) {
+ this.mainThemes = mainThemes;
+ }
+
+ public String getContentRecommendations() {
+ return contentRecommendations;
+ }
+
+ public void setContentRecommendations(String contentRecommendations) {
+ this.contentRecommendations = contentRecommendations;
+ }
+
+ public List getPriorityPlatforms() {
+ return priorityPlatforms;
+ }
+
+ public void setPriorityPlatforms(List 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 hashtags;
+
+ @Field("publish_time")
+ private String publishTime; // время публикации в формате HH:mm
+
+ public PostCalendarItem() {
+ }
+
+ public PostCalendarItem(LocalDateTime publishDate, String platform, String contentType, String theme, String postText, List 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 getHashtags() {
+ return hashtags;
+ }
+
+ public void setHashtags(List hashtags) {
+ this.hashtags = hashtags;
+ }
+
+ public String getPublishTime() {
+ return publishTime;
+ }
+
+ public void setPublishTime(String publishTime) {
+ this.publishTime = publishTime;
+ }
+ }
+}
+
diff --git a/src/main/java/kz/konturai/parser/repository/MarketingStrategyRepository.java b/src/main/java/kz/konturai/parser/repository/MarketingStrategyRepository.java
new file mode 100644
index 0000000..ab7bcb3
--- /dev/null
+++ b/src/main/java/kz/konturai/parser/repository/MarketingStrategyRepository.java
@@ -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 {
+ Optional findById(String id);
+ Optional findByAnalysisId(String analysisId);
+}
+
diff --git a/src/main/java/kz/konturai/parser/service/MarketingStrategyService.java b/src/main/java/kz/konturai/parser/service/MarketingStrategyService.java
new file mode 100644
index 0000000..bfa89ba
--- /dev/null
+++ b/src/main/java/kz/konturai/parser/service/MarketingStrategyService.java
@@ -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 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 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 weeklyPlans = generateWeeklyPlans(
+ context, strategy.getDurationWeeks(), strategy.getPriorityPlatforms());
+
+ // Generate post calendar
+ List 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 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 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 generateWeeklyPlans(
+ String context, Integer durationWeeks, List 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 jsonMap = objectMapper.readValue(jsonStr, new TypeReference