.
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
# Изменения в API маркетингового анализа
|
||||
|
||||
## Обзор изменений
|
||||
|
||||
Проведен рефакторинг метода генерации JSON-анализа для повышения детализации данных. Структура ответа API для фронтенда **осталась прежней**, но изменилась внутренняя структура данных в `chartsData`.
|
||||
|
||||
## Структура ответа API (без изменений)
|
||||
|
||||
Ответ API остается прежним:
|
||||
|
||||
```json
|
||||
{
|
||||
"analysisId": "string",
|
||||
"status": "completed",
|
||||
"createdAt": "2024-01-01T00:00:00",
|
||||
"completedAt": "2024-01-01T00:05:00",
|
||||
"report": {
|
||||
"summary": "string",
|
||||
"fullAnalysis": "string",
|
||||
"chartsData": {
|
||||
/* ← ИЗМЕНЕНИЯ ЗДЕСЬ */
|
||||
},
|
||||
"targetAudience": {
|
||||
"description": "string",
|
||||
"channels": ["Instagram", "Telegram"]
|
||||
},
|
||||
"recommendations": ["string"],
|
||||
"strategy": {
|
||||
"duration": "2 недели",
|
||||
"channels": ["Instagram", "Telegram"],
|
||||
"contentTypes": ["посты", "сторис"]
|
||||
},
|
||||
"pdfUrl": "/api/marketing/analysis/{id}/download"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Изменения в `chartsData`
|
||||
|
||||
### 1. Анализ аудитории (`audienceSegmentation`)
|
||||
|
||||
**Было:**
|
||||
|
||||
```json
|
||||
{
|
||||
"audienceSegmentation": {
|
||||
"ageGroups": [{ "label": "25-34", "value": "40" }],
|
||||
"genders": [{ "label": "Женщины", "value": "60" }],
|
||||
"segments": [{ "label": "Сегмент 1", "value": "35" }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Стало (расширенная структура):**
|
||||
|
||||
```json
|
||||
{
|
||||
"audienceSegmentation": {
|
||||
"ageGroups": [
|
||||
{ "label": "18-24", "value": "15" },
|
||||
{ "label": "25-34", "value": "40" },
|
||||
{ "label": "35-44", "value": "30" },
|
||||
{ "label": "45+", "value": "15" }
|
||||
],
|
||||
"genders": [
|
||||
{ "label": "Женщины", "value": "60" },
|
||||
{ "label": "Мужчины", "value": "40" }
|
||||
],
|
||||
"segments": [
|
||||
{
|
||||
"label": "Молодые профессионалы",
|
||||
"value": "35"
|
||||
}
|
||||
],
|
||||
"segmentsChannelMatrix": [
|
||||
// ← НОВОЕ
|
||||
{
|
||||
"segmentName": "Молодые профи",
|
||||
"instagram": "high",
|
||||
"telegram": "medium",
|
||||
"youtube": "low"
|
||||
}
|
||||
],
|
||||
"keyTakeaways": [
|
||||
// ← НОВОЕ
|
||||
"Вывод 1",
|
||||
"Вывод 2",
|
||||
"Вывод 3"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Что изменилось:**
|
||||
|
||||
- ✅ Структура `ageGroups`, `genders`, `segments` осталась прежней
|
||||
- ➕ Добавлено поле `segmentsChannelMatrix` — матрица соответствия сегментов и каналов (heatmap)
|
||||
- ➕ Добавлено поле `keyTakeaways` — ключевые выводы по аудитории
|
||||
|
||||
### 2. Анализ конкурентов (`competitors`)
|
||||
|
||||
**Было:**
|
||||
|
||||
```json
|
||||
{
|
||||
"competitors": [
|
||||
{
|
||||
"name": "Конкурент А",
|
||||
"reach": 50000,
|
||||
"activity": 85,
|
||||
"reviews": 1200,
|
||||
"strengths": "Не указано",
|
||||
"weaknesses": "Не указано"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Стало (расширенная структура):**
|
||||
|
||||
```json
|
||||
{
|
||||
"marketShareChart": [
|
||||
// ← НОВОЕ
|
||||
{ "name": "Наш бренд", "value": 25 },
|
||||
{ "name": "Конкурент А", "value": 30 },
|
||||
{ "name": "Конкурент B", "value": 20 },
|
||||
{ "name": "Другие", "value": 25 }
|
||||
],
|
||||
"competitors": [
|
||||
{
|
||||
"name": "Конкурент А",
|
||||
"reach": 50000,
|
||||
"followers": 12000, // ← НОВОЕ
|
||||
"activity": 85,
|
||||
"priceStrategy": "Высокая", // ← НОВОЕ
|
||||
"channelsHeatmap": {
|
||||
// ← НОВОЕ
|
||||
"Instagram": 90,
|
||||
"TikTok": 20,
|
||||
"Telegram": 70,
|
||||
"YouTube": 40
|
||||
}
|
||||
}
|
||||
],
|
||||
"comparisonTable": [
|
||||
// ← НОВОЕ
|
||||
{
|
||||
"feature": "Ценовая политика",
|
||||
"us": "Средняя",
|
||||
"compA": "Высокая",
|
||||
"compB": "Низкая"
|
||||
}
|
||||
],
|
||||
"swotCompetitors": {
|
||||
// ← НОВОЕ
|
||||
"strengths": ["..."],
|
||||
"weaknesses": ["..."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Что изменилось:**
|
||||
|
||||
- ✅ Массив `competitors` остался, но с дополнительными полями
|
||||
- ➕ Добавлено поле `marketShareChart` — доли рынка для графика
|
||||
- ➕ Добавлено поле `comparisonTable` — сравнительная таблица характеристик
|
||||
- ➕ Добавлено поле `swotCompetitors` — SWOT-анализ по рынку
|
||||
- ➕ В объектах конкурентов добавлены: `followers`, `priceStrategy`, `channelsHeatmap`
|
||||
|
||||
### 3. Сезонность (`seasonality`)
|
||||
|
||||
**Без изменений:**
|
||||
|
||||
```json
|
||||
{
|
||||
"seasonality": {
|
||||
"Январь": 45,
|
||||
"Февраль": 50,
|
||||
"Март": 60
|
||||
// ... остальные месяцы
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Рынок (`market`) — НОВОЕ
|
||||
|
||||
**Добавлено:**
|
||||
|
||||
```json
|
||||
{
|
||||
"market": {
|
||||
"size": "средний",
|
||||
"growthRate": "растущий",
|
||||
"trends": ["Тренд 1", "Тренд 2"],
|
||||
"opportunities": ["Возможность 1"],
|
||||
"threats": ["Угроза 1"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Обратная совместимость
|
||||
|
||||
Для обеспечения обратной совместимости, следующие поля могут присутствовать в `chartsData` (если использовалась старая структура):
|
||||
|
||||
- `channelsPotential` — каналы продвижения
|
||||
- `conversionFunnel` — воронка конверсии
|
||||
- `swot` — SWOT-анализ
|
||||
|
||||
**Рекомендация:** Используйте новые поля, но проверяйте наличие старых для обратной совместимости.
|
||||
|
||||
## Миграция фронтенда
|
||||
|
||||
### Шаг 1: Обновите обработку `audienceSegmentation`
|
||||
|
||||
```typescript
|
||||
// Было
|
||||
const ageGroups = chartsData.audienceSegmentation?.ageGroups || [];
|
||||
const genders = chartsData.audienceSegmentation?.genders || [];
|
||||
const segments = chartsData.audienceSegmentation?.segments || [];
|
||||
|
||||
// Стало (добавьте новые поля)
|
||||
const ageGroups = chartsData.audienceSegmentation?.ageGroups || [];
|
||||
const genders = chartsData.audienceSegmentation?.genders || [];
|
||||
const segments = chartsData.audienceSegmentation?.segments || [];
|
||||
const segmentsChannelMatrix =
|
||||
chartsData.audienceSegmentation?.segmentsChannelMatrix || []; // НОВОЕ
|
||||
const keyTakeaways = chartsData.audienceSegmentation?.keyTakeaways || []; // НОВОЕ
|
||||
```
|
||||
|
||||
### Шаг 2: Обновите обработку конкурентов
|
||||
|
||||
```typescript
|
||||
// Было
|
||||
const competitors = chartsData.competitors || [];
|
||||
|
||||
// Стало (добавьте новые поля)
|
||||
const marketShareChart = chartsData.marketShareChart || []; // НОВОЕ
|
||||
const competitors = chartsData.competitors || [];
|
||||
const comparisonTable = chartsData.comparisonTable || []; // НОВОЕ
|
||||
const swotCompetitors = chartsData.swotCompetitors || {}; // НОВОЕ
|
||||
|
||||
// Обновите обработку объектов конкурентов
|
||||
competitors.forEach((comp) => {
|
||||
const followers = comp.followers; // НОВОЕ
|
||||
const priceStrategy = comp.priceStrategy; // НОВОЕ
|
||||
const channelsHeatmap = comp.channelsHeatmap; // НОВОЕ
|
||||
});
|
||||
```
|
||||
|
||||
### Шаг 3: Добавьте обработку рынка
|
||||
|
||||
```typescript
|
||||
// НОВОЕ
|
||||
const market = chartsData.market || {};
|
||||
const marketSize = market.size;
|
||||
const growthRate = market.growthRate;
|
||||
const trends = market.trends || [];
|
||||
const opportunities = market.opportunities || [];
|
||||
const threats = market.threats || [];
|
||||
```
|
||||
|
||||
## Примеры использования новых данных
|
||||
|
||||
### 1. Heatmap сегментов и каналов
|
||||
|
||||
```typescript
|
||||
const segmentsChannelMatrix =
|
||||
chartsData.audienceSegmentation?.segmentsChannelMatrix || [];
|
||||
|
||||
// Отображение heatmap
|
||||
segmentsChannelMatrix.forEach((row) => {
|
||||
const segmentName = row.segmentName;
|
||||
const instagram = row.instagram; // "high" | "medium" | "low"
|
||||
const telegram = row.telegram;
|
||||
const youtube = row.youtube;
|
||||
// ... отрисовка heatmap
|
||||
});
|
||||
```
|
||||
|
||||
### 2. График долей рынка
|
||||
|
||||
```typescript
|
||||
const marketShareChart = chartsData.marketShareChart || [];
|
||||
|
||||
// Отображение pie chart
|
||||
marketShareChart.forEach((item) => {
|
||||
const name = item.name; // "Наш бренд", "Конкурент А", etc.
|
||||
const value = item.value; // процент доли рынка
|
||||
// ... отрисовка графика
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Сравнительная таблица
|
||||
|
||||
```typescript
|
||||
const comparisonTable = chartsData.comparisonTable || [];
|
||||
|
||||
// Отображение таблицы
|
||||
comparisonTable.forEach((row) => {
|
||||
const feature = row.feature; // "Ценовая политика", "УТП", etc.
|
||||
const us = row.us;
|
||||
const compA = row.compA;
|
||||
const compB = row.compB;
|
||||
// ... отрисовка таблицы
|
||||
});
|
||||
```
|
||||
|
||||
## Резюме изменений
|
||||
|
||||
| Поле | Статус | Описание |
|
||||
| --------------------------------- | ---------------- | -------------------------------------------------------------- |
|
||||
| `chartsData.audienceSegmentation` | ✅ Расширено | Добавлены `segmentsChannelMatrix` и `keyTakeaways` |
|
||||
| `chartsData.competitors` | ✅ Расширено | Добавлены поля `followers`, `priceStrategy`, `channelsHeatmap` |
|
||||
| `chartsData.marketShareChart` | ➕ Новое | График долей рынка |
|
||||
| `chartsData.comparisonTable` | ➕ Новое | Сравнительная таблица |
|
||||
| `chartsData.swotCompetitors` | ➕ Новое | SWOT-анализ по рынку |
|
||||
| `chartsData.market` | ➕ Новое | Данные о рынке |
|
||||
| `chartsData.seasonality` | ✅ Без изменений | Осталось прежним |
|
||||
|
||||
## Вопросы?
|
||||
|
||||
Если возникнут вопросы по миграции, обращайтесь к бэкенд-команде.
|
||||
@@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class MarketingAnalysisService {
|
||||
@@ -147,34 +149,100 @@ public class MarketingAnalysisService {
|
||||
private Map<String, Object> generateJsonAnalysis(MarketingAnalysisRequest request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
// 1. Получаем анализ целевой аудитории
|
||||
logger.info("Starting audience analysis generation");
|
||||
Map<String, Object> audienceAnalysis = getAudienceAnalysis(request);
|
||||
if (audienceAnalysis != null && !audienceAnalysis.isEmpty()) {
|
||||
result.putAll(audienceAnalysis);
|
||||
logger.info("Audience analysis generated successfully");
|
||||
} else {
|
||||
logger.warn("Audience analysis returned empty result");
|
||||
}
|
||||
logger.info("Starting parallel JSON analysis generation");
|
||||
|
||||
// 2. Получаем анализ конкурентов
|
||||
logger.info("Starting competitor analysis generation");
|
||||
Map<String, Object> competitorAnalysis = getCompetitorAnalysis(request);
|
||||
if (competitorAnalysis != null && !competitorAnalysis.isEmpty()) {
|
||||
result.putAll(competitorAnalysis);
|
||||
logger.info("Competitor analysis generated successfully");
|
||||
} else {
|
||||
logger.warn("Competitor analysis returned empty result");
|
||||
}
|
||||
// Создаем CompletableFuture для каждого типа анализа
|
||||
@SuppressWarnings("unchecked")
|
||||
CompletableFuture<Map<String, Object>> audienceFuture = (CompletableFuture<Map<String, Object>>) CompletableFuture
|
||||
.<Map<String, Object>>supplyAsync(() -> {
|
||||
logger.info("Starting audience analysis generation");
|
||||
Map<String, Object> analysis = getAudienceAnalysis(request);
|
||||
if (analysis != null && !analysis.isEmpty()) {
|
||||
logger.info("Audience analysis generated successfully");
|
||||
} else {
|
||||
logger.warn("Audience analysis returned empty result");
|
||||
}
|
||||
return analysis != null ? analysis : new HashMap<>();
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
logger.error("Error in audience analysis future: {}", ex.getMessage(), ex);
|
||||
return new HashMap<>();
|
||||
});
|
||||
|
||||
// 3. Получаем анализ сезонности и рынка
|
||||
logger.info("Starting seasonality and market analysis generation");
|
||||
Map<String, Object> seasonalityAndMarket = getSeasonalityAndMarket(request);
|
||||
if (seasonalityAndMarket != null && !seasonalityAndMarket.isEmpty()) {
|
||||
result.putAll(seasonalityAndMarket);
|
||||
logger.info("Seasonality and market analysis generated successfully");
|
||||
} else {
|
||||
logger.warn("Seasonality and market analysis returned empty result");
|
||||
@SuppressWarnings("unchecked")
|
||||
CompletableFuture<Map<String, Object>> competitorFuture = (CompletableFuture<Map<String, Object>>) CompletableFuture
|
||||
.<Map<String, Object>>supplyAsync(() -> {
|
||||
logger.info("Starting competitor analysis generation");
|
||||
Map<String, Object> analysis = getCompetitorAnalysis(request);
|
||||
if (analysis != null && !analysis.isEmpty()) {
|
||||
logger.info("Competitor analysis generated successfully");
|
||||
} else {
|
||||
logger.warn("Competitor analysis returned empty result");
|
||||
}
|
||||
return analysis != null ? analysis : new HashMap<>();
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
logger.error("Error in competitor analysis future: {}", ex.getMessage(), ex);
|
||||
return new HashMap<>();
|
||||
});
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
CompletableFuture<Map<String, Object>> seasonalityFuture = (CompletableFuture<Map<String, Object>>) CompletableFuture
|
||||
.<Map<String, Object>>supplyAsync(() -> {
|
||||
logger.info("Starting seasonality and market analysis generation");
|
||||
Map<String, Object> analysis = getSeasonalityAndMarket(request);
|
||||
if (analysis != null && !analysis.isEmpty()) {
|
||||
logger.info("Seasonality and market analysis generated successfully");
|
||||
} else {
|
||||
logger.warn("Seasonality and market analysis returned empty result");
|
||||
}
|
||||
return analysis != null ? analysis : new HashMap<>();
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
logger.error("Error in seasonality analysis future: {}", ex.getMessage(), ex);
|
||||
return new HashMap<>();
|
||||
});
|
||||
|
||||
// Ожидаем завершения всех futures
|
||||
try {
|
||||
CompletableFuture.allOf(audienceFuture, competitorFuture, seasonalityFuture)
|
||||
.get(5, TimeUnit.MINUTES); // Таймаут 5 минут
|
||||
|
||||
// Собираем результаты
|
||||
Map<String, Object> audienceAnalysis = audienceFuture.join();
|
||||
Map<String, Object> competitorAnalysis = competitorFuture.join();
|
||||
Map<String, Object> seasonalityAndMarket = seasonalityFuture.join();
|
||||
|
||||
if (audienceAnalysis != null && !audienceAnalysis.isEmpty()) {
|
||||
result.putAll(audienceAnalysis);
|
||||
}
|
||||
if (competitorAnalysis != null && !competitorAnalysis.isEmpty()) {
|
||||
result.putAll(competitorAnalysis);
|
||||
}
|
||||
if (seasonalityAndMarket != null && !seasonalityAndMarket.isEmpty()) {
|
||||
result.putAll(seasonalityAndMarket);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error waiting for analysis futures: {}", e.getMessage(), e);
|
||||
// Пытаемся получить результаты даже при ошибке
|
||||
try {
|
||||
Map<String, Object> audienceAnalysis = audienceFuture.getNow(new HashMap<>());
|
||||
Map<String, Object> competitorAnalysis = competitorFuture.getNow(new HashMap<>());
|
||||
Map<String, Object> seasonalityAndMarket = seasonalityFuture.getNow(new HashMap<>());
|
||||
|
||||
if (audienceAnalysis != null && !audienceAnalysis.isEmpty()) {
|
||||
result.putAll(audienceAnalysis);
|
||||
}
|
||||
if (competitorAnalysis != null && !competitorAnalysis.isEmpty()) {
|
||||
result.putAll(competitorAnalysis);
|
||||
}
|
||||
if (seasonalityAndMarket != null && !seasonalityAndMarket.isEmpty()) {
|
||||
result.putAll(seasonalityAndMarket);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("Error getting partial results: {}", ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем, что хотя бы одна секция была успешно сгенерирована
|
||||
@@ -209,6 +277,7 @@ public class MarketingAnalysisService {
|
||||
|
||||
/**
|
||||
* Генерация анализа целевой аудитории
|
||||
* Модульный метод с глубоким психологическим профилированием сегментов
|
||||
*/
|
||||
private Map<String, Object> getAudienceAnalysis(MarketingAnalysisRequest request) {
|
||||
try {
|
||||
@@ -216,9 +285,15 @@ public class MarketingAnalysisService {
|
||||
|
||||
StringBuilder promptBuilder = new StringBuilder();
|
||||
promptBuilder.append(
|
||||
"Ты — профессиональный маркетинговый аналитик, работающий с малым и средним бизнесом в Казахстане.\n\n");
|
||||
"Ты — профессиональный маркетинговый аналитик и психолог, работающий с малым и средним бизнесом в Казахстане.\n\n");
|
||||
promptBuilder.append(
|
||||
"Твоя задача — на основе входных данных о бизнесе выполнить глубокий анализ целевой аудитории и выдать результат ТОЛЬКО в виде корректного JSON-объекта строго по указанной структуре.\n\n");
|
||||
"Твоя задача — на основе входных данных о бизнесе выполнить ГЛУБОКИЙ анализ целевой аудитории с психологическим профилированием сегментов.\n\n");
|
||||
promptBuilder.append(
|
||||
"КРИТИЧЕСКИ ВАЖНО: Проведи глубокое психологическое профилирование. Распиши мотивацию и барьеры подробно. "
|
||||
+ "Не используй общие фразы типа 'хотят качество' или 'боятся потратить деньги'. "
|
||||
+ "Опиши КОНКРЕТНЫЕ психологические механизмы, жизненные ситуации, эмоциональные триггеры и реальные препятствия для каждого сегмента.\n\n");
|
||||
promptBuilder.append(
|
||||
"Выдай результат ТОЛЬКО в виде корректного JSON-объекта строго по указанной структуре.\n\n");
|
||||
promptBuilder.append("Не добавляй пояснений, комментариев, текста до или после JSON.\n");
|
||||
promptBuilder.append("Ответ должен быть валидным JSON.\n\n");
|
||||
promptBuilder.append("---\n\n");
|
||||
@@ -265,44 +340,67 @@ public class MarketingAnalysisService {
|
||||
promptBuilder.append("- Регион/город: ").append(regionStr).append("\n");
|
||||
promptBuilder.append("- Цель продвижения: ").append(request.getGoal()).append("\n\n");
|
||||
|
||||
promptBuilder.append("СФОРМИРУЙ JSON СЛЕДУЮЩЕЙ СТРУКТУРЫ:\n\n");
|
||||
promptBuilder.append("СФОРМИРУЙ JSON СЛЕДУЮЩЕЙ СТРУКТУРЫ (СТРОГО СОБЛЮДАЙ ФОРМАТ):\n\n");
|
||||
promptBuilder.append("{\n");
|
||||
promptBuilder.append("\"audienceAnalysis\": {\n");
|
||||
promptBuilder.append(" \"audienceAnalysis\": {\n");
|
||||
promptBuilder.append(
|
||||
"\"ageGroups\": { \"18-24\": число, \"25-34\": число, \"35-44\": число, \"45+\": число }, // Pie chart data, сумма = 100\n");
|
||||
promptBuilder.append("\"ageComment\": \"Текст вывода под графиком возраста (1-2 предложения)\",\n");
|
||||
" \"ageGroups\": { \"18-24\": число, \"25-34\": число, \"35-44\": число, \"45+\": число },\n");
|
||||
promptBuilder.append(
|
||||
"\"genderDistribution\": { \"Женщины\": число, \"Мужчины\": число }, // Donut chart, сумма = 100\n");
|
||||
promptBuilder.append("\"genderComment\": \"Текст вывода под графиком пола\",\n");
|
||||
promptBuilder.append("\"segments\": [ // Список из 4 детальных карточек сегментов\n");
|
||||
promptBuilder.append("{\n");
|
||||
promptBuilder.append("\"name\": \"Название (например 'Молодые профессионалы')\",\n");
|
||||
promptBuilder.append("\"sharePercent\": число, // сумма всех sharePercent = 100\n");
|
||||
promptBuilder.append("\"ageRange\": \"25-34\",\n");
|
||||
promptBuilder.append("\"incomeLevel\": \"Средний+\",\n");
|
||||
promptBuilder.append("\"motivation\": \"Ключевая мотивация покупки\",\n");
|
||||
promptBuilder.append("\"triggers\": \"Триггеры (на что реагируют)\",\n");
|
||||
promptBuilder
|
||||
.append("\"primaryChannels\": [\"Instagram\", \"TikTok\"], // Топ каналы для этого сегмента\n");
|
||||
promptBuilder.append("\"secondaryChannels\": [\"Telegram\"]\n");
|
||||
promptBuilder.append("}\n");
|
||||
promptBuilder.append("],\n");
|
||||
promptBuilder.append("\"segmentsChannelMatrix\": [ // Для таблицы heatmap: Сегмент vs Канал\n");
|
||||
" \"ageComment\": \"Подробный вывод о возрасте (2-3 предложения с конкретными инсайтами о том, почему именно эти возрастные группы являются целевыми)\",\n");
|
||||
promptBuilder.append(" \"genderDistribution\": { \"Женщины\": число, \"Мужчины\": число },\n");
|
||||
promptBuilder.append(
|
||||
"{ \"segmentName\": \"Молодые профи\", \"instagram\": \"high\", \"telegram\": \"medium\", \"youtube\": \"low\" }\n");
|
||||
promptBuilder.append("],\n");
|
||||
promptBuilder.append("\"keyTakeaways\": [\"Вывод 1\", \"Вывод 2\", \"Вывод 3\"]\n");
|
||||
promptBuilder.append("}\n");
|
||||
" \"genderComment\": \"Подробный вывод о гендере (2-3 предложения с конкретными инсайтами о гендерных особенностях потребления)\",\n");
|
||||
promptBuilder.append(" \"segments\": [\n");
|
||||
promptBuilder.append(" {\n");
|
||||
promptBuilder.append(" \"name\": \"Название сегмента (например 'Молодые мамы')\",\n");
|
||||
promptBuilder.append(" \"sharePercent\": число,\n");
|
||||
promptBuilder.append(" \"ageRange\": \"25-34\",\n");
|
||||
promptBuilder.append(" \"incomeLevel\": \"Средний\",\n");
|
||||
promptBuilder.append(
|
||||
" \"motivation\": \"Детальное описание мотивации покупки (3-4 предложения). Опиши психологические и практические причины, почему этот сегмент покупает продукт/услугу. "
|
||||
+ "Используй конкретные примеры жизненных ситуаций, эмоциональных потребностей, ценностей. НЕ используй общие фразы.\",\n");
|
||||
promptBuilder.append(
|
||||
" \"triggers\": \"Список триггеров (что заставляет действовать). Опиши конкретные ситуации, события, эмоции, которые побуждают к покупке (2-3 предложения). "
|
||||
+ "Например: 'Сезонные события (Новый год, 8 марта), жизненные изменения (рождение ребенка, переезд), эмоциональные состояния (стресс, радость, желание перемен)'.\",\n");
|
||||
promptBuilder.append(
|
||||
" \"barriers\": \"Что мешает купить (страхи, сомнения, препятствия). Опиши конкретные барьеры: финансовые, психологические, информационные (2-3 предложения). "
|
||||
+ "Например: 'Опасение переплатить, недоверие к онлайн-покупкам, отсутствие времени на изучение предложений, страх выбрать неправильно'.\",\n");
|
||||
promptBuilder.append(" \"primaryChannels\": [\"Instagram\", \"TikTok\"],\n");
|
||||
promptBuilder.append(" \"secondaryChannels\": [\"Telegram\"]\n");
|
||||
promptBuilder.append(" }\n");
|
||||
promptBuilder.append(" ],\n");
|
||||
promptBuilder.append(" \"segmentsChannelMatrix\": [\n");
|
||||
promptBuilder.append(
|
||||
" { \"segmentName\": \"Молодые мамы\", \"Instagram\": \"High\", \"Facebook\": \"Medium\", \"YouTube\": \"Low\", \"TikTok\": \"High\", \"Telegram\": \"Low\" }\n");
|
||||
promptBuilder.append(" ],\n");
|
||||
promptBuilder.append(
|
||||
" \"keyTakeaways\": [\"Глубокий вывод 1 (1-2 предложения с конкретным инсайтом)\", \"Глубокий вывод 2 (1-2 предложения с конкретным инсайтом)\", \"Глубокий вывод 3 (1-2 предложения с конкретным инсайтом)\"]\n");
|
||||
promptBuilder.append(" }\n");
|
||||
promptBuilder.append("}\n\n");
|
||||
promptBuilder.append("ТРЕБОВАНИЯ:\n\n");
|
||||
promptBuilder.append("- Верни ТОЛЬКО JSON, без пояснений.\n");
|
||||
promptBuilder.append("- Следи, чтобы JSON был синтаксически корректным.\n");
|
||||
promptBuilder.append("- Сумма значений в ageGroups должна быть = 100.\n");
|
||||
promptBuilder.append("- Сумма значений в genderDistribution должна быть = 100.\n");
|
||||
promptBuilder.append("- Сумма sharePercent по всем segments должна быть = 100.\n");
|
||||
promptBuilder.append("- Все строки — на русском языке.\n");
|
||||
promptBuilder.append("КРИТИЧЕСКИ ВАЖНЫЕ ТРЕБОВАНИЯ:\n\n");
|
||||
promptBuilder.append("1. Верни ТОЛЬКО JSON, без пояснений, комментариев, markdown-разметки.\n");
|
||||
promptBuilder.append("2. JSON должен быть синтаксически корректным и валидным.\n");
|
||||
promptBuilder.append("3. Сумма значений в ageGroups должна быть = 100.\n");
|
||||
promptBuilder.append("4. Сумма значений в genderDistribution должна быть = 100.\n");
|
||||
promptBuilder.append("5. Сумма sharePercent по всем segments должна быть = 100.\n");
|
||||
promptBuilder.append("6. Минимум 4 подробных сегмента в массиве segments.\n");
|
||||
promptBuilder.append(
|
||||
"7. Для каждого сегмента заполни ВСЕ поля: name, sharePercent, ageRange, incomeLevel, motivation, triggers, barriers, primaryChannels, secondaryChannels.\n");
|
||||
promptBuilder.append(
|
||||
"8. Поля motivation, triggers, barriers должны содержать ДЕТАЛЬНЫЕ описания (не общие фразы, а конкретные инсайты с примерами). "
|
||||
+ "Каждое поле должно быть минимум 2-3 предложения с конкретными деталями.\n");
|
||||
promptBuilder.append(
|
||||
"9. В segmentsChannelMatrix должна быть строка для КАЖДОГО сегмента из массива segments. "
|
||||
+ "Используй значения \"High\", \"Medium\", \"Low\" (с заглавной буквы) для каждого канала: Instagram, Facebook, YouTube, TikTok, Telegram.\n");
|
||||
promptBuilder.append("10. Все строки — на русском языке.\n");
|
||||
promptBuilder.append(
|
||||
"11. keyTakeaways должны содержать глубокие инсайты, а не общие фразы. Каждый вывод должен быть конкретным и практичным.\n");
|
||||
promptBuilder.append(
|
||||
"12. segmentsChannelMatrix должна отражать реальное поведение каждого сегмента в социальных сетях. "
|
||||
+ "High = сегмент активно использует канал, Medium = умеренное использование, Low = редкое использование.\n");
|
||||
|
||||
String prompt = promptBuilder.toString();
|
||||
logger.debug("Generating audience analysis with detailed prompt (length: {} chars)", prompt.length());
|
||||
String jsonResponse = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru");
|
||||
|
||||
if (jsonResponse == null || jsonResponse.trim().isEmpty()) {
|
||||
@@ -314,6 +412,7 @@ public class MarketingAnalysisService {
|
||||
Map<String, Object> jsonMap = objectMapper.readValue(cleanedJson, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
|
||||
logger.info("Audience analysis generated successfully");
|
||||
return jsonMap;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error generating audience analysis: {}", e.getMessage(), e);
|
||||
@@ -333,6 +432,10 @@ public class MarketingAnalysisService {
|
||||
"Ты — профессиональный маркетинговый аналитик, работающий с малым и средним бизнесом в Казахстане.\n\n");
|
||||
promptBuilder.append(
|
||||
"Твоя задача — на основе входных данных о бизнесе выполнить глубокий анализ конкурентов и выдать результат ТОЛЬКО в виде корректного JSON-объекта строго по указанной структуре.\n\n");
|
||||
promptBuilder.append(
|
||||
"КРИТИЧЕСКИ ВАЖНО: Найди или смоделируй реалистичных конкурентов для этой ниши. "
|
||||
+ "Оцени их цифровые метрики и контент-стратегию. Если ты не знаешь реальных конкурентов, "
|
||||
+ "создай реалистичные модели на основе типичных игроков в этой нише в Казахстане.\n\n");
|
||||
promptBuilder.append("Не добавляй пояснений, комментариев, текста до или после JSON.\n");
|
||||
promptBuilder.append("Ответ должен быть валидным JSON.\n\n");
|
||||
promptBuilder.append("---\n\n");
|
||||
@@ -352,44 +455,74 @@ public class MarketingAnalysisService {
|
||||
|
||||
promptBuilder.append("СФОРМИРУЙ JSON СЛЕДУЮЩЕЙ СТРУКТУРЫ:\n\n");
|
||||
promptBuilder.append("{\n");
|
||||
promptBuilder.append("\"competitorAnalysis\": {\n");
|
||||
promptBuilder.append("\"marketShareChart\": [ // Оценочные доли рынка для графика\n");
|
||||
promptBuilder.append("{ \"name\": \"Наш бренд\", \"value\": число },\n");
|
||||
promptBuilder.append("{ \"name\": \"Конкурент А\", \"value\": число },\n");
|
||||
promptBuilder.append("{ \"name\": \"Конкурент B\", \"value\": число },\n");
|
||||
promptBuilder.append("{ \"name\": \"Другие\", \"value\": число }\n");
|
||||
promptBuilder.append("], // сумма всех value должна быть = 100\n");
|
||||
promptBuilder.append("\"marketShareComment\": \"Вывод по доле рынка\",\n");
|
||||
promptBuilder.append("\"competitorsDetails\": [ // Детали по 3-4 конкурентам\n");
|
||||
promptBuilder.append("{\n");
|
||||
promptBuilder.append("\"name\": \"Конкурент А\",\n");
|
||||
promptBuilder.append("\"priceStrategy\": \"Высокая/Средняя/Низкая\",\n");
|
||||
promptBuilder.append(" \"competitorAnalysis\": {\n");
|
||||
promptBuilder.append(" \"marketShareChart\": [ // Оценочные доли рынка для графика\n");
|
||||
promptBuilder.append(" { \"name\": \"Наш бренд\", \"value\": число },\n");
|
||||
promptBuilder.append(" { \"name\": \"Конкурент А\", \"value\": число },\n");
|
||||
promptBuilder.append(" { \"name\": \"Конкурент B\", \"value\": число },\n");
|
||||
promptBuilder.append(" { \"name\": \"Другие\", \"value\": число }\n");
|
||||
promptBuilder.append(" ], // сумма всех value должна быть = 100\n");
|
||||
promptBuilder.append(
|
||||
"\"digitalMetrics\": { \"reach\": число, \"followers\": число, \"activityIndex\": число }, // activityIndex от 0 до 100\n");
|
||||
promptBuilder.append("\"channelsHeatmap\": { // Оценка (0-100) качества ведения канала\n");
|
||||
promptBuilder.append("\"Instagram\": число, \"TikTok\": число, \"Telegram\": число, \"YouTube\": число\n");
|
||||
promptBuilder.append("}\n");
|
||||
promptBuilder.append("}\n");
|
||||
promptBuilder.append("],\n");
|
||||
promptBuilder.append("\"comparisonTable\": [ // Сравнительная таблица (5-6 характеристик)\n");
|
||||
" \"marketShareComment\": \"Детальный анализ позиций на рынке (2-3 предложения с конкретными инсайтами)\",\n");
|
||||
promptBuilder.append(" \"competitorsDetails\": [ // Детали по 3-4 конкурентам\n");
|
||||
promptBuilder.append(" {\n");
|
||||
promptBuilder.append(" \"name\": \"Конкурент А\",\n");
|
||||
promptBuilder.append(" \"digitalMetrics\": { \"reach\": число, \"followers\": число, \"activityIndex\": число }, // activityIndex от 0 до 100\n");
|
||||
promptBuilder.append(" \"channelsHeatmap\": { // Оценка (0-100) качества ведения канала\n");
|
||||
promptBuilder.append(" \"Instagram\": число, \"TikTok\": число, \"Telegram\": число, \"YouTube\": число, \"Facebook\": число\n");
|
||||
promptBuilder.append(" },\n");
|
||||
promptBuilder.append(" \"priceStrategy\": \"Премиум/Средняя/Эконом\",\n");
|
||||
promptBuilder.append(
|
||||
"{ \"feature\": \"Ценовая политика\", \"us\": \"Средняя\", \"compA\": \"Высокая\", \"compB\": \"Низкая\" },\n");
|
||||
" \"contentStrategy\": \"Детальное описание контент-стратегии конкурента (3-4 предложения). "
|
||||
+ "Опиши типы контента, стиль подачи, частоту публикаций, основные темы, тон коммуникации. "
|
||||
+ "Проанализируй, что работает у конкурента и почему.\",\n");
|
||||
promptBuilder.append(" \"swot\": {\n");
|
||||
promptBuilder.append(
|
||||
"{ \"feature\": \"УТП\", \"us\": \"Сервис\", \"compA\": \"Бренд\", \"compB\": \"Дешевизна\" }\n");
|
||||
promptBuilder.append("],\n");
|
||||
promptBuilder.append("\"swotCompetitors\": { // Краткий SWOT по рынку в целом\n");
|
||||
promptBuilder.append("\"strengths\": [\"...\"], \"weaknesses\": [\"...\"]\n");
|
||||
promptBuilder.append("},\n");
|
||||
promptBuilder.append("\"keyInsights\": [\"Вывод 1\", \"Вывод 2\"]\n");
|
||||
promptBuilder.append("}\n");
|
||||
" \"strengths\": [\"Конкретная сильная сторона 1\", \"Конкретная сильная сторона 2\", \"Конкретная сильная сторона 3\"],\n");
|
||||
promptBuilder.append(
|
||||
" \"weaknesses\": [\"Конкретная слабая сторона 1\", \"Конкретная слабая сторона 2\", \"Конкретная слабая сторона 3\"]\n");
|
||||
promptBuilder.append(" }\n");
|
||||
promptBuilder.append(" }\n");
|
||||
promptBuilder.append(" ],\n");
|
||||
promptBuilder.append(" \"comparisonTable\": [ // Сравнительная таблица (5-6 характеристик)\n");
|
||||
promptBuilder.append(
|
||||
" { \"feature\": \"Цена\", \"us\": \"Средняя\", \"compA\": \"Высокая\", \"compB\": \"Низкая\" },\n");
|
||||
promptBuilder.append(
|
||||
" { \"feature\": \"Продукт\", \"us\": \"Онлайн\", \"compA\": \"Офлайн\", \"compB\": \"Гибрид\" },\n");
|
||||
promptBuilder.append(
|
||||
" { \"feature\": \"УТП\", \"us\": \"Сервис\", \"compA\": \"Бренд\", \"compB\": \"Дешевизна\" },\n");
|
||||
promptBuilder.append(
|
||||
" { \"feature\": \"Целевая аудитория\", \"us\": \"...\", \"compA\": \"...\", \"compB\": \"...\" },\n");
|
||||
promptBuilder.append(
|
||||
" { \"feature\": \"Каналы продвижения\", \"us\": \"...\", \"compA\": \"...\", \"compB\": \"...\" },\n");
|
||||
promptBuilder.append(
|
||||
" { \"feature\": \"Позиционирование\", \"us\": \"...\", \"compA\": \"...\", \"compB\": \"...\" }\n");
|
||||
promptBuilder.append(" ],\n");
|
||||
promptBuilder.append(
|
||||
" \"keyInsights\": [\"Глубокий инсайт 1 (1-2 предложения)\", \"Глубокий инсайт 2 (1-2 предложения)\"]\n");
|
||||
promptBuilder.append(" }\n");
|
||||
promptBuilder.append("}\n\n");
|
||||
promptBuilder.append("ТРЕБОВАНИЯ:\n\n");
|
||||
promptBuilder.append("- Верни ТОЛЬКО JSON, без пояснений.\n");
|
||||
promptBuilder.append("- Следи, чтобы JSON был синтаксически корректным.\n");
|
||||
promptBuilder.append("- Сумма всех value в marketShareChart должна быть = 100.\n");
|
||||
promptBuilder.append("- Все строки — на русском языке.\n");
|
||||
promptBuilder.append("КРИТИЧЕСКИ ВАЖНЫЕ ТРЕБОВАНИЯ:\n\n");
|
||||
promptBuilder.append("1. Верни ТОЛЬКО JSON, без пояснений, комментариев, markdown-разметки.\n");
|
||||
promptBuilder.append("2. JSON должен быть синтаксически корректным и валидным.\n");
|
||||
promptBuilder.append("3. Сумма всех value в marketShareChart должна быть = 100.\n");
|
||||
promptBuilder.append("4. В массиве competitorsDetails должно быть 3-4 детальных описания конкурентов.\n");
|
||||
promptBuilder.append(
|
||||
"5. Для каждого конкурента заполни ВСЕ поля: name, digitalMetrics, channelsHeatmap, priceStrategy, contentStrategy, swot.\n");
|
||||
promptBuilder.append(
|
||||
"6. Поле contentStrategy должно содержать ДЕТАЛЬНОЕ описание (3-4 предложения), а не общие фразы.\n");
|
||||
promptBuilder.append(
|
||||
"7. В swot для каждого конкурента укажи конкретные strengths и weaknesses (по 3-4 пункта), а не общие формулировки.\n");
|
||||
promptBuilder.append("8. В comparisonTable должно быть 5-6 строк сравнения различных характеристик.\n");
|
||||
promptBuilder.append(
|
||||
"9. marketShareComment должен содержать детальный анализ позиций на рынке (2-3 предложения).\n");
|
||||
promptBuilder.append("10. keyInsights должны содержать глубокие инсайты, а не общие фразы.\n");
|
||||
promptBuilder.append("11. Все строки — на русском языке.\n");
|
||||
promptBuilder.append(
|
||||
"12. channelsHeatmap должна содержать оценки (0-100) для всех основных каналов: Instagram, TikTok, Telegram, YouTube, Facebook.\n");
|
||||
|
||||
String prompt = promptBuilder.toString();
|
||||
logger.debug("Generating competitor analysis with detailed prompt (length: {} chars)", prompt.length());
|
||||
String jsonResponse = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru");
|
||||
|
||||
if (jsonResponse == null || jsonResponse.trim().isEmpty()) {
|
||||
@@ -401,6 +534,7 @@ public class MarketingAnalysisService {
|
||||
Map<String, Object> jsonMap = objectMapper.readValue(cleanedJson, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
|
||||
logger.info("Competitor analysis generated successfully");
|
||||
return jsonMap;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error generating competitor analysis: {}", e.getMessage(), e);
|
||||
@@ -420,6 +554,9 @@ public class MarketingAnalysisService {
|
||||
"Ты — профессиональный маркетинговый аналитик, работающий с малым и средним бизнесом в Казахстане.\n\n");
|
||||
promptBuilder.append(
|
||||
"Твоя задача — на основе входных данных о бизнесе выполнить глубокий анализ сезонности и рынка и выдать результат ТОЛЬКО в виде корректного JSON-объекта строго по указанной структуре.\n\n");
|
||||
promptBuilder.append(
|
||||
"КРИТИЧЕСКИ ВАЖНО: Предоставь детальный анализ сезонных колебаний спроса с конкретными объяснениями. "
|
||||
+ "Опиши рыночные тренды, возможности и угрозы с конкретными инсайтами, а не общими фразами.\n\n");
|
||||
promptBuilder.append("Не добавляй пояснений, комментариев, текста до или после JSON.\n");
|
||||
promptBuilder.append("Ответ должен быть валидным JSON.\n\n");
|
||||
promptBuilder.append("---\n\n");
|
||||
@@ -437,29 +574,51 @@ public class MarketingAnalysisService {
|
||||
|
||||
promptBuilder.append("СФОРМИРУЙ JSON СЛЕДУЮЩЕЙ СТРУКТУРЫ:\n\n");
|
||||
promptBuilder.append("{\n");
|
||||
promptBuilder.append("\"seasonality\": {\n");
|
||||
promptBuilder.append("\"description\": \"Описание сезонных колебаний спроса\",\n");
|
||||
promptBuilder.append(" \"seasonality\": {\n");
|
||||
promptBuilder.append(
|
||||
"\"months\": [ \"Январь\", \"Февраль\", \"Март\", \"Апрель\", \"Май\", \"Июнь\", \"Июль\", \"Август\", \"Сентябрь\", \"Октябрь\", \"Ноябрь\", \"Декабрь\" ],\n");
|
||||
promptBuilder.append("\"demandIndex\": [12 чисел от 0 до 100], // Индекс спроса по месяцам\n");
|
||||
promptBuilder.append("\"estimated\": boolean\n");
|
||||
promptBuilder.append("},\n");
|
||||
promptBuilder.append("\"market\": {\n");
|
||||
promptBuilder.append("\"size\": \"Размер рынка (например, 'средний', 'крупный')\",\n");
|
||||
promptBuilder.append("\"growthRate\": \"Темп роста рынка\",\n");
|
||||
promptBuilder.append("\"trends\": [\"Тренд 1\", \"Тренд 2\", \"Тренд 3\"],\n");
|
||||
promptBuilder.append("\"opportunities\": [\"Возможность 1\", \"Возможность 2\"],\n");
|
||||
promptBuilder.append("\"threats\": [\"Угроза 1\", \"Угроза 2\"],\n");
|
||||
promptBuilder.append("\"estimated\": boolean\n");
|
||||
promptBuilder.append("}\n");
|
||||
" \"months\": [ \"Январь\", \"Февраль\", \"Март\", \"Апрель\", \"Май\", \"Июнь\", \"Июль\", \"Август\", \"Сентябрь\", \"Октябрь\", \"Ноябрь\", \"Декабрь\" ],\n");
|
||||
promptBuilder.append(" \"demandIndex\": [12 чисел от 0 до 100], // Индекс спроса по месяцам\n");
|
||||
promptBuilder.append(
|
||||
" \"description\": \"Детальное описание сезонности (3-4 предложения). Опиши конкретные причины сезонных колебаний: "
|
||||
+ "праздники, климатические условия, культурные особенности, жизненные циклы целевой аудитории. "
|
||||
+ "Укажи, какие месяцы являются пиковыми и почему, какие - низкими и что это означает для бизнеса.\"\n");
|
||||
promptBuilder.append(" },\n");
|
||||
promptBuilder.append(" \"market\": {\n");
|
||||
promptBuilder.append(
|
||||
" \"marketSize\": \"Оценка объема рынка (например, 'Оценка объема рынка составляет X млн тенге в год' или 'Средний рынок с потенциалом роста'). "
|
||||
+ "Дай конкретную оценку или описание масштаба рынка в Казахстане для данной ниши.\",\n");
|
||||
promptBuilder.append(
|
||||
" \"growthRate\": \"Темп роста рынка (например, '+15% в год' или 'Стабильный рост 10-12% ежегодно')\",\n");
|
||||
promptBuilder.append(
|
||||
" \"trends\": [\"Конкретный тренд 1 (1-2 предложения с деталями)\", \"Конкретный тренд 2 (1-2 предложения с деталями)\", \"Конкретный тренд 3 (1-2 предложения с деталями)\"],\n");
|
||||
promptBuilder.append(
|
||||
" \"opportunities\": [\"Конкретная возможность 1 (1-2 предложения с деталями)\", \"Конкретная возможность 2 (1-2 предложения с деталями)\"],\n");
|
||||
promptBuilder.append(
|
||||
" \"threats\": [\"Конкретная угроза 1 (1-2 предложения с деталями)\", \"Конкретная угроза 2 (1-2 предложения с деталями)\"]\n");
|
||||
promptBuilder.append(" }\n");
|
||||
promptBuilder.append("}\n\n");
|
||||
promptBuilder.append("ТРЕБОВАНИЯ:\n\n");
|
||||
promptBuilder.append("- Верни ТОЛЬКО JSON, без пояснений.\n");
|
||||
promptBuilder.append("- Следи, чтобы JSON был синтаксически корректным.\n");
|
||||
promptBuilder.append("- Значения \"demandIndex\" должны быть от 0 до 100.\n");
|
||||
promptBuilder.append("- Все строки — на русском языке.\n");
|
||||
promptBuilder.append("КРИТИЧЕСКИ ВАЖНЫЕ ТРЕБОВАНИЯ:\n\n");
|
||||
promptBuilder.append("1. Верни ТОЛЬКО JSON, без пояснений, комментариев, markdown-разметки.\n");
|
||||
promptBuilder.append("2. JSON должен быть синтаксически корректным и валидным.\n");
|
||||
promptBuilder.append("3. Значения \"demandIndex\" должны быть от 0 до 100 (12 чисел для каждого месяца).\n");
|
||||
promptBuilder.append(
|
||||
"4. Поле \"description\" в seasonality должно содержать ДЕТАЛЬНОЕ описание (3-4 предложения) с конкретными причинами сезонных колебаний.\n");
|
||||
promptBuilder.append(
|
||||
"5. Поле \"marketSize\" должно содержать конкретную оценку объема рынка, а не просто слова 'средний' или 'крупный'.\n");
|
||||
promptBuilder.append(
|
||||
"6. Массивы trends, opportunities, threats должны содержать КОНКРЕТНЫЕ инсайты (по 1-2 предложения каждый), а не общие фразы.\n");
|
||||
promptBuilder.append(
|
||||
"7. Каждый элемент в trends должен описывать реальный тренд с конкретными деталями (например, 'Рост популярности онлайн-заказов на 30% за последний год').\n");
|
||||
promptBuilder.append(
|
||||
"8. Каждый элемент в opportunities должен описывать конкретную возможность с практическими деталями.\n");
|
||||
promptBuilder.append(
|
||||
"9. Каждый элемент в threats должен описывать конкретную угрозу с практическими деталями.\n");
|
||||
promptBuilder.append("10. Все строки — на русском языке.\n");
|
||||
promptBuilder.append(
|
||||
"11. НЕ включай поле \"estimated\" ни в seasonality, ни в market.\n");
|
||||
|
||||
String prompt = promptBuilder.toString();
|
||||
logger.debug("Generating seasonality and market analysis with detailed prompt (length: {} chars)", prompt.length());
|
||||
String jsonResponse = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru");
|
||||
|
||||
if (jsonResponse == null || jsonResponse.trim().isEmpty()) {
|
||||
@@ -471,6 +630,7 @@ public class MarketingAnalysisService {
|
||||
Map<String, Object> jsonMap = objectMapper.readValue(cleanedJson, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
|
||||
logger.info("Seasonality and market analysis generated successfully");
|
||||
return jsonMap;
|
||||
} catch (Exception e) {
|
||||
logger.error("Error generating seasonality and market analysis: {}", e.getMessage(), e);
|
||||
@@ -984,6 +1144,8 @@ public class MarketingAnalysisService {
|
||||
|
||||
/**
|
||||
* Парсинг данных для графиков из JSON-анализа
|
||||
* Обновлено для работы с новой структурой: audienceAnalysis,
|
||||
* competitorAnalysis, seasonality, market
|
||||
*/
|
||||
private Map<String, Object> parseChartDataFromJson(Map<String, Object> analysisJson) {
|
||||
Map<String, Object> chartsData = new HashMap<>();
|
||||
@@ -993,7 +1155,7 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Сезонность
|
||||
// 1. Сезонность (структура не изменилась)
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> seasonality = (Map<String, Object>) analysisJson.get("seasonality");
|
||||
if (seasonality != null) {
|
||||
@@ -1017,97 +1179,143 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Конкуренты
|
||||
// 2. Конкуренты (новая структура: competitorAnalysis)
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> competitors = (List<Map<String, Object>>) analysisJson.get("competitors");
|
||||
if (competitors != null && !competitors.isEmpty()) {
|
||||
List<Map<String, Object>> competitorsList = new ArrayList<>();
|
||||
for (Map<String, Object> comp : competitors) {
|
||||
Map<String, Object> competitorData = new HashMap<>();
|
||||
competitorData.put("name", comp.getOrDefault("name", "Неизвестный конкурент"));
|
||||
competitorData.put("reach", comp.getOrDefault("reach", 10000));
|
||||
competitorData.put("activity", comp.getOrDefault("socialActivityIndex", 50));
|
||||
competitorData.put("reviews", comp.getOrDefault("reviewsCount", 100));
|
||||
competitorData.put("strengths", comp.getOrDefault("strengths", "Не указано"));
|
||||
competitorData.put("weaknesses", comp.getOrDefault("weaknesses", "Не указано"));
|
||||
competitorsList.add(competitorData);
|
||||
Map<String, Object> competitorAnalysis = (Map<String, Object>) analysisJson.get("competitorAnalysis");
|
||||
if (competitorAnalysis != null) {
|
||||
// 2.1. Market Share Chart
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> marketShareChart = (List<Map<String, Object>>) competitorAnalysis
|
||||
.get("marketShareChart");
|
||||
if (marketShareChart != null && !marketShareChart.isEmpty()) {
|
||||
chartsData.put("marketShareChart", marketShareChart);
|
||||
}
|
||||
|
||||
// 2.2. Competitors Details (аналог старого competitors)
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> competitorsDetails = (List<Map<String, Object>>) competitorAnalysis
|
||||
.get("competitorsDetails");
|
||||
if (competitorsDetails != null && !competitorsDetails.isEmpty()) {
|
||||
List<Map<String, Object>> competitorsList = new ArrayList<>();
|
||||
for (Map<String, Object> comp : competitorsDetails) {
|
||||
Map<String, Object> competitorData = new HashMap<>();
|
||||
competitorData.put("name", comp.getOrDefault("name", "Неизвестный конкурент"));
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> digitalMetrics = (Map<String, Object>) comp.get("digitalMetrics");
|
||||
if (digitalMetrics != null) {
|
||||
competitorData.put("reach", digitalMetrics.getOrDefault("reach", 10000));
|
||||
competitorData.put("followers", digitalMetrics.getOrDefault("followers", 0));
|
||||
competitorData.put("activity", digitalMetrics.getOrDefault("activityIndex", 50));
|
||||
} else {
|
||||
competitorData.put("reach", 10000);
|
||||
competitorData.put("followers", 0);
|
||||
competitorData.put("activity", 50);
|
||||
}
|
||||
|
||||
competitorData.put("priceStrategy", comp.getOrDefault("priceStrategy", "Не указано"));
|
||||
competitorData.put("channelsHeatmap", comp.getOrDefault("channelsHeatmap", new HashMap<>()));
|
||||
competitorsList.add(competitorData);
|
||||
}
|
||||
chartsData.put("competitors", competitorsList);
|
||||
}
|
||||
|
||||
// 2.3. Comparison Table
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> comparisonTable = (List<Map<String, Object>>) competitorAnalysis
|
||||
.get("comparisonTable");
|
||||
if (comparisonTable != null && !comparisonTable.isEmpty()) {
|
||||
chartsData.put("comparisonTable", comparisonTable);
|
||||
}
|
||||
|
||||
// 2.4. SWOT Competitors (частичный SWOT из competitorAnalysis)
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> swotCompetitors = (Map<String, Object>) competitorAnalysis.get("swotCompetitors");
|
||||
if (swotCompetitors != null) {
|
||||
chartsData.put("swotCompetitors", swotCompetitors);
|
||||
}
|
||||
chartsData.put("competitors", competitorsList);
|
||||
}
|
||||
|
||||
// 3. Сегментация аудитории
|
||||
// 3. Анализ аудитории (новая структура: audienceAnalysis)
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> audienceSegments = (Map<String, Object>) analysisJson.get("audienceSegments");
|
||||
if (audienceSegments != null) {
|
||||
Map<String, Object> audienceAnalysis = (Map<String, Object>) analysisJson.get("audienceAnalysis");
|
||||
if (audienceAnalysis != null) {
|
||||
Map<String, Object> segmentationData = new HashMap<>();
|
||||
|
||||
// 3.1. Age Groups (готовые данные из новой структуры)
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> segments = (List<Map<String, Object>>) audienceSegments.get("segments");
|
||||
if (segments != null && !segments.isEmpty()) {
|
||||
Map<String, Object> segmentationData = new HashMap<>();
|
||||
|
||||
// Группировка по возрасту
|
||||
Map<String, Integer> ageGroups = new HashMap<>();
|
||||
Map<String, Integer> genders = new HashMap<>();
|
||||
List<Map<String, String>> segmentsList = new ArrayList<>();
|
||||
|
||||
for (Map<String, Object> segment : segments) {
|
||||
String name = (String) segment.getOrDefault("name", "Сегмент");
|
||||
Object percentObj = segment.get("sharePercent");
|
||||
int percent = percentObj instanceof Number ? ((Number) percentObj).intValue() : 0;
|
||||
|
||||
String ageRange = (String) segment.getOrDefault("ageRange", "");
|
||||
String gender = (String) segment.getOrDefault("gender", "");
|
||||
|
||||
// Добавляем в общий список сегментов
|
||||
Map<String, String> segmentData = new HashMap<>();
|
||||
segmentData.put("label", name);
|
||||
segmentData.put("value", String.valueOf(percent));
|
||||
segmentsList.add(segmentData);
|
||||
|
||||
// Группируем по возрасту
|
||||
if (!ageRange.isEmpty()) {
|
||||
ageGroups.put(ageRange, ageGroups.getOrDefault(ageRange, 0) + percent);
|
||||
}
|
||||
|
||||
// Группируем по полу
|
||||
if (!gender.isEmpty()) {
|
||||
genders.put(gender, genders.getOrDefault(gender, 0) + percent);
|
||||
}
|
||||
}
|
||||
|
||||
// Преобразуем в нужный формат
|
||||
Map<String, Object> ageGroupsMap = (Map<String, Object>) audienceAnalysis.get("ageGroups");
|
||||
if (ageGroupsMap != null) {
|
||||
List<Map<String, String>> ageGroupsList = new ArrayList<>();
|
||||
for (Map.Entry<String, Integer> entry : ageGroups.entrySet()) {
|
||||
for (Map.Entry<String, Object> entry : ageGroupsMap.entrySet()) {
|
||||
Map<String, String> ageGroup = new HashMap<>();
|
||||
ageGroup.put("label", entry.getKey());
|
||||
ageGroup.put("value", String.valueOf(entry.getValue()));
|
||||
ageGroupsList.add(ageGroup);
|
||||
}
|
||||
segmentationData.put("ageGroups", ageGroupsList);
|
||||
}
|
||||
|
||||
// 3.2. Gender Distribution (готовые данные из новой структуры)
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> genderDistributionMap = (Map<String, Object>) audienceAnalysis
|
||||
.get("genderDistribution");
|
||||
if (genderDistributionMap != null) {
|
||||
List<Map<String, String>> gendersList = new ArrayList<>();
|
||||
for (Map.Entry<String, Integer> entry : genders.entrySet()) {
|
||||
for (Map.Entry<String, Object> entry : genderDistributionMap.entrySet()) {
|
||||
Map<String, String> genderData = new HashMap<>();
|
||||
genderData.put("label", entry.getKey());
|
||||
genderData.put("value", String.valueOf(entry.getValue()));
|
||||
gendersList.add(genderData);
|
||||
}
|
||||
segmentationData.put("genders", gendersList);
|
||||
}
|
||||
|
||||
if (!ageGroupsList.isEmpty()) {
|
||||
segmentationData.put("ageGroups", ageGroupsList);
|
||||
}
|
||||
if (!gendersList.isEmpty()) {
|
||||
segmentationData.put("genders", gendersList);
|
||||
}
|
||||
if (!segmentsList.isEmpty()) {
|
||||
segmentationData.put("segments", segmentsList);
|
||||
// 3.3. Segments (детальные карточки сегментов)
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> segments = (List<Map<String, Object>>) audienceAnalysis.get("segments");
|
||||
if (segments != null && !segments.isEmpty()) {
|
||||
List<Map<String, String>> segmentsList = new ArrayList<>();
|
||||
for (Map<String, Object> segment : segments) {
|
||||
Map<String, String> segmentData = new HashMap<>();
|
||||
segmentData.put("label", (String) segment.getOrDefault("name", "Сегмент"));
|
||||
Object percentObj = segment.get("sharePercent");
|
||||
segmentData.put("value", String.valueOf(percentObj instanceof Number ? percentObj : 0));
|
||||
segmentsList.add(segmentData);
|
||||
}
|
||||
segmentationData.put("segments", segmentsList);
|
||||
}
|
||||
|
||||
if (!segmentationData.isEmpty()) {
|
||||
chartsData.put("audienceSegmentation", segmentationData);
|
||||
}
|
||||
// 3.4. Segments Channel Matrix (heatmap)
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> segmentsChannelMatrix = (List<Map<String, Object>>) audienceAnalysis
|
||||
.get("segmentsChannelMatrix");
|
||||
if (segmentsChannelMatrix != null && !segmentsChannelMatrix.isEmpty()) {
|
||||
segmentationData.put("segmentsChannelMatrix", segmentsChannelMatrix);
|
||||
}
|
||||
|
||||
// 3.5. Key Takeaways
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> keyTakeaways = (List<String>) audienceAnalysis.get("keyTakeaways");
|
||||
if (keyTakeaways != null && !keyTakeaways.isEmpty()) {
|
||||
segmentationData.put("keyTakeaways", keyTakeaways);
|
||||
}
|
||||
|
||||
if (!segmentationData.isEmpty()) {
|
||||
chartsData.put("audienceSegmentation", segmentationData);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Каналы продвижения
|
||||
// 4. Рынок (новая секция)
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> market = (Map<String, Object>) analysisJson.get("market");
|
||||
if (market != null) {
|
||||
chartsData.put("market", market);
|
||||
}
|
||||
|
||||
// 5. Обратная совместимость: проверяем старую структуру для channels, funnel,
|
||||
// swot
|
||||
// (на случай, если где-то еще используется старая структура)
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> channels = (List<Map<String, Object>>) analysisJson.get("channels");
|
||||
if (channels != null && !channels.isEmpty()) {
|
||||
@@ -1122,7 +1330,6 @@ public class MarketingAnalysisService {
|
||||
chartsData.put("channelsPotential", channelsList);
|
||||
}
|
||||
|
||||
// 5. Воронка конверсии
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> funnel = (Map<String, Object>) analysisJson.get("funnel");
|
||||
if (funnel != null) {
|
||||
@@ -1147,7 +1354,6 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
}
|
||||
|
||||
// 6. SWOT (для таблицы)
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> swot = (Map<String, Object>) analysisJson.get("swot");
|
||||
if (swot != null) {
|
||||
|
||||
Reference in New Issue
Block a user