.
This commit is contained in:
@@ -1403,6 +1403,89 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует профессиональный текстовый маркетинговый отчет на основе
|
||||
* JSON-анализа.
|
||||
* Делает ОДИН вызов AI для создания цельного, глубокого отчета.
|
||||
*
|
||||
* @param analysisJson JSON с данными маркетингового анализа (включая product,
|
||||
* businessNiche, goal)
|
||||
* @return Профессиональный текстовый отчет с плейсхолдерами для графиков
|
||||
*/
|
||||
private String generatePolishedTextReport(Map<String, Object> analysisJson) {
|
||||
logger.info("generatePolishedTextReport: Starting professional text report generation");
|
||||
|
||||
if (analysisJson == null || analysisJson.isEmpty()) {
|
||||
logger.warn("generatePolishedTextReport: Empty analysisJson provided");
|
||||
return "Не удалось сгенерировать текстовый отчёт: отсутствуют данные JSON-анализа.";
|
||||
}
|
||||
|
||||
try {
|
||||
String jsonString = objectMapper.writeValueAsString(analysisJson);
|
||||
logger.debug("generatePolishedTextReport: JSON string length: {}", jsonString.length());
|
||||
|
||||
StringBuilder promptBuilder = new StringBuilder();
|
||||
promptBuilder.append("Ты — опытный маркетинговый аналитик и бизнес-консультант.\n\n");
|
||||
promptBuilder.append("На основе следующего JSON с маркетинговым анализом напиши ПРОФЕССИОНАЛЬНЫЙ, ");
|
||||
promptBuilder.append("ГЛУБОКИЙ маркетинговый отчет на русском языке.\n\n");
|
||||
|
||||
promptBuilder.append("ТРЕБОВАНИЯ К ОТЧЕТУ:\n");
|
||||
promptBuilder.append("1. Используй деловой стиль письма, убедительный для владельца бизнеса\n");
|
||||
promptBuilder.append("2. Добавляй аналитические инсайты и практические выводы\n");
|
||||
promptBuilder.append("3. Структурируй текст по разделам с римскими цифрами (I, II, III и т.д.)\n");
|
||||
promptBuilder.append("4. Каждый раздел должен быть содержательным (минимум 2-3 абзаца)\n");
|
||||
promptBuilder.append("5. Используй данные ТОЛЬКО из переданного JSON, не выдумывай цифры\n");
|
||||
promptBuilder.append("6. Пиши конкретно и практично, избегай общих фраз\n\n");
|
||||
|
||||
promptBuilder.append("ОБЯЗАТЕЛЬНАЯ СТРУКТУРА ОТЧЕТА:\n");
|
||||
promptBuilder.append("I. Краткое резюме (общий обзор бизнеса и ключевые выводы)\n");
|
||||
promptBuilder.append("II. Анализ продукта (особенности, позиционирование, УТП)\n");
|
||||
promptBuilder.append("III. Анализ рынка и сезонности (тренды, динамика, сезонные колебания)\n");
|
||||
promptBuilder.append(" [[CHART_SEASONALITY]]\n");
|
||||
promptBuilder.append("IV. Анализ целевой аудитории (сегменты, психографика, мотивация)\n");
|
||||
promptBuilder.append(" [[CHART_AUDIENCE]]\n");
|
||||
promptBuilder.append("V. Анализ конкурентов (позиции, стратегии, сильные и слабые стороны)\n");
|
||||
promptBuilder.append(" [[CHART_COMPETITORS]]\n");
|
||||
promptBuilder.append("VI. SWOT-анализ (сильные стороны, слабые стороны, возможности, угрозы)\n");
|
||||
promptBuilder.append(" [[CHART_SWOT]]\n");
|
||||
promptBuilder.append("VII. Каналы продвижения (рекомендуемые каналы с обоснованием)\n");
|
||||
promptBuilder.append(" [[CHART_CHANNELS]]\n");
|
||||
promptBuilder.append("VIII. Воронка спроса (этапы, конверсии, рекомендации)\n");
|
||||
promptBuilder.append(" [[CHART_FUNNEL]]\n");
|
||||
promptBuilder.append("IX. Рекомендации по позиционированию и УТП\n");
|
||||
promptBuilder.append("X. Рекомендации по контенту (типы контента для разных целей)\n");
|
||||
promptBuilder.append("XI. Итоговая стратегия: что делать в первую очередь (конкретный план действий)\n\n");
|
||||
|
||||
promptBuilder.append("ВАЖНО:\n");
|
||||
promptBuilder.append("- Плейсхолдеры [[CHART_...]] вставляй ТОЧНО как указано выше, на отдельной строке\n");
|
||||
promptBuilder.append("- Начинай сразу с 'I. Краткое резюме', без вступлений\n");
|
||||
promptBuilder.append(
|
||||
"- НЕ добавляй секции 'Целевая аудитория', 'Рекомендации', 'Стратегия' отдельно в конце — они уже включены в структуру\n");
|
||||
promptBuilder.append("- Пиши так, чтобы отчет был полезен для принятия бизнес-решений\n\n");
|
||||
|
||||
promptBuilder.append("JSON ДАННЫЕ:\n");
|
||||
promptBuilder.append(jsonString);
|
||||
|
||||
String prompt = promptBuilder.toString();
|
||||
logger.info("generatePolishedTextReport: Calling AI for text report generation");
|
||||
|
||||
String result = openAIAnalyticsService.generateWithInstruction(jsonString, prompt, "ru");
|
||||
|
||||
if (result == null || result.trim().isEmpty()) {
|
||||
logger.warn("generatePolishedTextReport: AI returned empty result");
|
||||
return "Не удалось сгенерировать текстовый отчёт.";
|
||||
}
|
||||
|
||||
logger.info("generatePolishedTextReport: Text report generated successfully. Length: {} chars",
|
||||
result.length());
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("generatePolishedTextReport: Error generating text report: {}", e.getMessage(), e);
|
||||
return "Ошибка при генерации текстового отчёта: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Этап 1: Рынок и Продукт
|
||||
* Разделы: I. Краткое резюме, II. Анализ продукта, III. Анализ рынка и
|
||||
@@ -2331,56 +2414,9 @@ public class MarketingAnalysisService {
|
||||
.map(AnalysisType::getDisplayName)
|
||||
.toList();
|
||||
|
||||
// Build context for AI with all new fields
|
||||
StringBuilder contextBuilder = new StringBuilder();
|
||||
contextBuilder.append("Ниша бизнеса: ").append(request.getBusinessNiche()).append("\n");
|
||||
contextBuilder.append("Продукт/услуга: ").append(request.getProduct()).append("\n");
|
||||
|
||||
// Format target audience
|
||||
Map<String, Object> targetAudienceData = request.getTargetAudience();
|
||||
if (targetAudienceData != null) {
|
||||
StringBuilder audienceBuilder = new StringBuilder();
|
||||
if (targetAudienceData.containsKey("genders")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> genders = (List<String>) targetAudienceData.get("genders");
|
||||
if (genders != null && !genders.isEmpty()) {
|
||||
audienceBuilder.append("Гендер: ").append(String.join(", ", genders)).append(". ");
|
||||
}
|
||||
}
|
||||
if (targetAudienceData.containsKey("ageRanges")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> ageRanges = (List<String>) targetAudienceData.get("ageRanges");
|
||||
if (ageRanges != null && !ageRanges.isEmpty()) {
|
||||
audienceBuilder.append("Возраст: ").append(String.join(", ", ageRanges)).append(". ");
|
||||
}
|
||||
}
|
||||
if (targetAudienceData.containsKey("types")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> types = (List<String>) targetAudienceData.get("types");
|
||||
if (types != null && !types.isEmpty()) {
|
||||
audienceBuilder.append("Тип: ").append(String.join(", ", types)).append(". ");
|
||||
}
|
||||
}
|
||||
contextBuilder.append("Целевая аудитория: ").append(audienceBuilder.toString().trim()).append("\n");
|
||||
}
|
||||
|
||||
// Format regions
|
||||
List<String> regions = request.getRegion();
|
||||
if (regions != null && !regions.isEmpty()) {
|
||||
contextBuilder.append("Регион: ").append(String.join(", ", regions)).append("\n");
|
||||
}
|
||||
contextBuilder.append("Цель на 6-12 месяцев: ").append(request.getGoal()).append("\n");
|
||||
contextBuilder.append("Уровень детализации: ").append(detailLevelName).append("\n");
|
||||
if (request.getStrongSide() != null && !request.getStrongSide().trim().isEmpty()) {
|
||||
contextBuilder.append("Сильная сторона: ").append(request.getStrongSide()).append("\n");
|
||||
}
|
||||
if (request.getWeakSide() != null && !request.getWeakSide().trim().isEmpty()) {
|
||||
contextBuilder.append("Слабая сторона: ").append(request.getWeakSide()).append("\n");
|
||||
}
|
||||
contextBuilder.append("Типы анализа: ").append(String.join(", ", analysisTypeNames)).append("\n");
|
||||
String context = contextBuilder.toString();
|
||||
|
||||
// Шаг 1: Генерация JSON-анализа (Промпт №1)
|
||||
// Контекст (product, businessNiche, goal, regions) добавляется в analysisJson
|
||||
// после генерации
|
||||
logger.info("generateMarketingReport: Starting JSON analysis generation");
|
||||
Map<String, Object> analysisJson = generateJsonAnalysis(request);
|
||||
|
||||
@@ -2392,17 +2428,40 @@ public class MarketingAnalysisService {
|
||||
report.put("fullAnalysis", "Не удалось сгенерировать анализ. Пожалуйста, попробуйте позже.");
|
||||
report.put("summary", "Не удалось сгенерировать анализ. Пожалуйста, попробуйте позже.");
|
||||
report.put("chartsData", new HashMap<>());
|
||||
|
||||
// Добавляем дефолтные значения для обратной совместимости
|
||||
Map<String, Object> defaultTargetAudience = new HashMap<>();
|
||||
defaultTargetAudience.put("description", "Не удалось сгенерировать описание целевой аудитории.");
|
||||
defaultTargetAudience.put("channels", Arrays.asList("Instagram", "LinkedIn", "Telegram"));
|
||||
report.put("targetAudience", defaultTargetAudience);
|
||||
report.put("recommendations", Arrays.asList("Не удалось сгенерировать рекомендации."));
|
||||
|
||||
Map<String, Object> defaultStrategy = new HashMap<>();
|
||||
defaultStrategy.put("duration", "1 месяц");
|
||||
defaultStrategy.put("channels", Arrays.asList("Instagram", "Telegram", "LinkedIn"));
|
||||
defaultStrategy.put("contentTypes", Arrays.asList("посты", "сторис", "видео"));
|
||||
report.put("strategy", defaultStrategy);
|
||||
|
||||
logger.warn("generateMarketingReport: Returning report with error message");
|
||||
} else {
|
||||
logger.info("generateMarketingReport: JSON analysis generated successfully. Keys: {}",
|
||||
analysisJson.keySet());
|
||||
|
||||
// Добавляем контекст продукта в analysisJson для использования в AI-генерации
|
||||
// текста
|
||||
analysisJson.put("product", request.getProduct());
|
||||
analysisJson.put("businessNiche", request.getBusinessNiche());
|
||||
analysisJson.put("goal", request.getGoal());
|
||||
analysisJson.put("regions", request.getRegion());
|
||||
analysisJson.put("detailLevel", detailLevelName);
|
||||
|
||||
// Сохраняем JSON в отчёт
|
||||
report.put("analysisJson", analysisJson);
|
||||
|
||||
// Шаг 2: Глубокая генерация текстового отчёта на основе JSON (4 этапа)
|
||||
logger.info("generateMarketingReport: Starting deep text report generation");
|
||||
String textReport = generateDeepTextReport(analysisJson);
|
||||
logger.info("generateMarketingReport: Deep text report generated. Length: {} chars",
|
||||
// Шаг 2: Генерация профессионального текстового отчёта (ОДИН вызов AI)
|
||||
logger.info("generateMarketingReport: Starting polished text report generation");
|
||||
String textReport = generatePolishedTextReport(analysisJson);
|
||||
logger.info("generateMarketingReport: Polished text report generated. Length: {} chars",
|
||||
textReport != null ? textReport.length() : 0);
|
||||
|
||||
// Извлекаем данные для графиков из JSON
|
||||
@@ -2445,106 +2504,25 @@ public class MarketingAnalysisService {
|
||||
processedTextReport != null ? processedTextReport : "Анализ не удалось сгенерировать."); // Keep for
|
||||
// backward
|
||||
// compatibility
|
||||
|
||||
// Извлекаем данные из analysisJson вместо повторных вызовов AI
|
||||
logger.info("generateMarketingReport: Extracting targetAudience from analysisJson");
|
||||
Map<String, Object> targetAudience = extractTargetAudienceFromJson(analysisJson);
|
||||
report.put("targetAudience", targetAudience);
|
||||
logger.info("generateMarketingReport: TargetAudience extracted. Channels count: {}",
|
||||
((List<?>) targetAudience.get("channels")).size());
|
||||
|
||||
logger.info("generateMarketingReport: Extracting recommendations from analysisJson");
|
||||
List<String> recommendations = extractRecommendationsFromJson(analysisJson);
|
||||
report.put("recommendations", recommendations);
|
||||
logger.info("generateMarketingReport: Recommendations extracted. Count: {}", recommendations.size());
|
||||
|
||||
logger.info("generateMarketingReport: Extracting strategy from analysisJson");
|
||||
Map<String, Object> strategy = extractStrategyFromJson(analysisJson);
|
||||
report.put("strategy", strategy);
|
||||
logger.info("generateMarketingReport: Strategy extracted. Channels: {}", strategy.get("channels"));
|
||||
}
|
||||
|
||||
// Generate target audience description (adjusted by detail level)
|
||||
logger.info("generateMarketingReport: Starting target audience description generation");
|
||||
String audiencePrompt;
|
||||
if (detailLevel == DetailLevel.КРАТКО) {
|
||||
audiencePrompt = "На основе информации о бизнесе опиши целевую аудиторию кратко (1 абзац). " +
|
||||
"Укажи основные каналы коммуникации. Ответ должен быть на русском языке.\n\n" + context;
|
||||
} else if (detailLevel == DetailLevel.ПОДРОБНО) {
|
||||
audiencePrompt = "На основе информации о бизнесе проведи детальный анализ целевой аудитории (3-4 абзаца). "
|
||||
+
|
||||
"Включи демографические характеристики, психографический профиль, потребности, поведенческие паттерны. "
|
||||
+
|
||||
"Укажи, какие каналы коммуникации наиболее подходят для этой аудитории и почему. " +
|
||||
"Ответ должен быть на русском языке.\n\n" + context;
|
||||
} else {
|
||||
audiencePrompt = "На основе информации о бизнесе опиши целевую аудиторию (1-2 абзаца). " +
|
||||
"Укажи, какие каналы коммуникации наиболее подходят для этой аудитории. " +
|
||||
"Ответ должен быть на русском языке.\n\n" + context;
|
||||
}
|
||||
logger.debug("generateMarketingReport: Calling OpenAI for audience description");
|
||||
String audienceDescription = openAIAnalyticsService.generateWithInstruction(context, audiencePrompt, "ru");
|
||||
logger.info("generateMarketingReport: Audience description generated. Length: {} chars",
|
||||
audienceDescription != null ? audienceDescription.length() : 0);
|
||||
|
||||
// Extract channels from description or generate separately
|
||||
logger.info("generateMarketingReport: Starting channels generation");
|
||||
String channelsPrompt = "На основе описания бизнеса перечисли 3-5 наиболее подходящих маркетинговых каналов. " +
|
||||
"Ответ должен быть простым списком через запятую, без нумерации. Пример: Instagram, LinkedIn, Telegram\n\n"
|
||||
+ context;
|
||||
logger.debug("generateMarketingReport: Calling OpenAI for channels");
|
||||
String channelsStr = openAIAnalyticsService.generateWithInstruction(context, channelsPrompt, "ru");
|
||||
logger.debug("generateMarketingReport: Channels response received: {}", channelsStr);
|
||||
List<String> channels = parseChannels(channelsStr);
|
||||
logger.info("generateMarketingReport: Channels parsed. Count: {}", channels.size());
|
||||
|
||||
Map<String, Object> targetAudience = new HashMap<>();
|
||||
targetAudience.put("description", audienceDescription != null ? audienceDescription
|
||||
: "Описание целевой аудитории не удалось сгенерировать.");
|
||||
targetAudience.put("channels",
|
||||
channels.isEmpty() ? Arrays.asList("Instagram", "LinkedIn", "Telegram") : channels);
|
||||
report.put("targetAudience", targetAudience);
|
||||
|
||||
// Generate recommendations (adjusted by detail level)
|
||||
logger.info("generateMarketingReport: Starting recommendations generation");
|
||||
String recommendationsPrompt;
|
||||
if (detailLevel == DetailLevel.КРАТКО) {
|
||||
recommendationsPrompt = "На основе информации о бизнесе сформулируй 3-4 краткие практические рекомендации для маркетинговой стратегии. "
|
||||
+
|
||||
"Каждая рекомендация должна быть конкретной. " +
|
||||
"Ответ должен быть списком рекомендаций, каждая с новой строки, без нумерации.\n\n" + context;
|
||||
} else if (detailLevel == DetailLevel.ПОДРОБНО) {
|
||||
recommendationsPrompt = "На основе информации о бизнесе сформулируй 6-8 детальных практических рекомендаций для маркетинговой стратегии. "
|
||||
+
|
||||
"Каждая рекомендация должна быть конкретной, применимой и содержать обоснование. " +
|
||||
"Ответ должен быть списком рекомендаций, каждая с новой строки, без нумерации.\n\n" + context;
|
||||
} else {
|
||||
recommendationsPrompt = "На основе информации о бизнесе сформулируй 4-6 практических рекомендаций для маркетинговой стратегии. "
|
||||
+
|
||||
"Каждая рекомендация должна быть конкретной и применимой. " +
|
||||
"Ответ должен быть списком рекомендаций, каждая с новой строки, без нумерации.\n\n" + context;
|
||||
}
|
||||
logger.debug("generateMarketingReport: Calling OpenAI for recommendations");
|
||||
String recommendationsStr = openAIAnalyticsService.generateWithInstruction(context, recommendationsPrompt,
|
||||
"ru");
|
||||
logger.debug("generateMarketingReport: Recommendations response received. Length: {} chars",
|
||||
recommendationsStr != null ? recommendationsStr.length() : 0);
|
||||
List<String> recommendations = parseRecommendations(recommendationsStr);
|
||||
logger.info("generateMarketingReport: Recommendations parsed. Count: {}", recommendations.size());
|
||||
report.put("recommendations",
|
||||
recommendations.isEmpty() ? Arrays.asList("Рекомендации не удалось сгенерировать.") : recommendations);
|
||||
|
||||
// Generate strategy (adjusted by detail level)
|
||||
logger.info("generateMarketingReport: Starting strategy generation");
|
||||
String strategyPrompt;
|
||||
if (detailLevel == DetailLevel.КРАТКО) {
|
||||
strategyPrompt = "На основе информации о бизнесе создай краткую маркетинговую стратегию. " +
|
||||
"Укажи рекомендуемую длительность кампании (например, '2 недели', '1 месяц'), " +
|
||||
"3-4 канала коммуникации и типы контента (например, 'посты', 'сторис', 'баннеры'). " +
|
||||
"Ответ должен быть кратким структурированным текстом на русском языке.\n\n" + context;
|
||||
} else if (detailLevel == DetailLevel.ПОДРОБНО) {
|
||||
strategyPrompt = "На основе информации о бизнесе создай детальную маркетинговую стратегию. " +
|
||||
"Укажи рекомендуемую длительность кампании с обоснованием (например, '2 недели', '1 месяц'), " +
|
||||
"5-7 каналов коммуникации с описанием их преимуществ, типы контента (например, 'посты', 'сторис', 'баннеры') "
|
||||
+
|
||||
"и примерный план действий. Ответ должен быть подробным структурированным текстом на русском языке.\n\n"
|
||||
+ context;
|
||||
} else {
|
||||
strategyPrompt = "На основе информации о бизнесе создай маркетинговую стратегию. " +
|
||||
"Укажи рекомендуемую длительность кампании (например, '2 недели', '1 месяц'), " +
|
||||
"3-5 каналов коммуникации и типы контента (например, 'посты', 'сторис', 'баннеры'). " +
|
||||
"Ответ должен быть структурированным текстом на русском языке.\n\n" + context;
|
||||
}
|
||||
logger.debug("generateMarketingReport: Calling OpenAI for strategy");
|
||||
String strategyText = openAIAnalyticsService.generateWithInstruction(context, strategyPrompt, "ru");
|
||||
logger.info("generateMarketingReport: Strategy generated. Length: {} chars",
|
||||
strategyText != null ? strategyText.length() : 0);
|
||||
|
||||
Map<String, Object> strategy = parseStrategy(strategyText, channels);
|
||||
report.put("strategy", strategy);
|
||||
logger.info("generateMarketingReport: Report generation completed. Report keys: {}", report.keySet());
|
||||
|
||||
return report;
|
||||
@@ -2697,6 +2675,269 @@ public class MarketingAnalysisService {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает описание целевой аудитории и каналы из JSON-анализа.
|
||||
* Заменяет повторный вызов AI для генерации описания аудитории.
|
||||
*
|
||||
* @param analysisJson JSON с данными маркетингового анализа
|
||||
* @return Map с description и channels
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> extractTargetAudienceFromJson(Map<String, Object> analysisJson) {
|
||||
Map<String, Object> targetAudience = new HashMap<>();
|
||||
StringBuilder descriptionBuilder = new StringBuilder();
|
||||
List<String> channels = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// Извлекаем данные из audienceAnalysis
|
||||
Map<String, Object> audienceAnalysis = (Map<String, Object>) analysisJson.get("audienceAnalysis");
|
||||
if (audienceAnalysis != null) {
|
||||
// Получаем ключевые выводы
|
||||
List<String> keyTakeaways = (List<String>) audienceAnalysis.get("keyTakeaways");
|
||||
if (keyTakeaways != null && !keyTakeaways.isEmpty()) {
|
||||
descriptionBuilder.append("Ключевые характеристики целевой аудитории:\n");
|
||||
for (String takeaway : keyTakeaways) {
|
||||
descriptionBuilder.append("• ").append(takeaway).append("\n");
|
||||
}
|
||||
descriptionBuilder.append("\n");
|
||||
}
|
||||
|
||||
// Получаем информацию о сегментах
|
||||
List<Map<String, Object>> segments = (List<Map<String, Object>>) audienceAnalysis.get("segments");
|
||||
if (segments != null && !segments.isEmpty()) {
|
||||
descriptionBuilder.append("Основные сегменты аудитории:\n");
|
||||
for (Map<String, Object> segment : segments) {
|
||||
Object name = segment.get("name");
|
||||
Object sharePercent = segment.get("sharePercent");
|
||||
Object ageRange = segment.get("ageRange");
|
||||
Object motivation = segment.get("motivation");
|
||||
|
||||
if (name != null) {
|
||||
descriptionBuilder.append("\n• ").append(name);
|
||||
if (sharePercent != null) {
|
||||
descriptionBuilder.append(" (").append(sharePercent).append("%)");
|
||||
}
|
||||
descriptionBuilder.append("\n");
|
||||
}
|
||||
if (ageRange != null) {
|
||||
descriptionBuilder.append(" Возраст: ").append(ageRange).append("\n");
|
||||
}
|
||||
if (motivation != null) {
|
||||
descriptionBuilder.append(" Мотивация: ").append(motivation).append("\n");
|
||||
}
|
||||
|
||||
// Собираем каналы из сегментов
|
||||
List<String> primaryChannels = (List<String>) segment.get("primaryChannels");
|
||||
if (primaryChannels != null) {
|
||||
for (String channel : primaryChannels) {
|
||||
if (!channels.contains(channel)) {
|
||||
channels.add(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Комментарии по гендеру и возрасту
|
||||
Object genderComment = audienceAnalysis.get("genderComment");
|
||||
Object ageComment = audienceAnalysis.get("ageComment");
|
||||
if (genderComment != null || ageComment != null) {
|
||||
descriptionBuilder.append("\n");
|
||||
if (ageComment != null) {
|
||||
descriptionBuilder.append(ageComment).append("\n");
|
||||
}
|
||||
if (genderComment != null) {
|
||||
descriptionBuilder.append(genderComment).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем каналы из channels.recommended
|
||||
Map<String, Object> channelsData = (Map<String, Object>) analysisJson.get("channels");
|
||||
if (channelsData != null) {
|
||||
List<Map<String, Object>> recommended = (List<Map<String, Object>>) channelsData.get("recommended");
|
||||
if (recommended != null) {
|
||||
for (Map<String, Object> channel : recommended) {
|
||||
Object channelName = channel.get("name");
|
||||
if (channelName != null && !channels.contains(channelName.toString())) {
|
||||
channels.add(channelName.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("extractTargetAudienceFromJson: Error extracting data: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Если описание пустое, добавляем дефолтное
|
||||
if (descriptionBuilder.length() == 0) {
|
||||
descriptionBuilder.append("Описание целевой аудитории доступно в полном отчете.");
|
||||
}
|
||||
|
||||
// Если каналы пустые, добавляем дефолтные
|
||||
if (channels.isEmpty()) {
|
||||
channels = Arrays.asList("Instagram", "LinkedIn", "Telegram");
|
||||
}
|
||||
|
||||
targetAudience.put("description", descriptionBuilder.toString());
|
||||
targetAudience.put("channels", channels);
|
||||
|
||||
return targetAudience;
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает рекомендации из JSON-анализа.
|
||||
* Заменяет повторный вызов AI для генерации рекомендаций.
|
||||
*
|
||||
* @param analysisJson JSON с данными маркетингового анализа
|
||||
* @return Список рекомендаций
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<String> extractRecommendationsFromJson(Map<String, Object> analysisJson) {
|
||||
List<String> recommendations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// Извлекаем из contentRecommendations
|
||||
Map<String, Object> contentRecommendations = (Map<String, Object>) analysisJson
|
||||
.get("contentRecommendations");
|
||||
if (contentRecommendations != null) {
|
||||
// Собираем рекомендации из всех типов контента
|
||||
List<String> salesContent = (List<String>) contentRecommendations.get("sales");
|
||||
List<String> expertContent = (List<String>) contentRecommendations.get("expertise");
|
||||
List<String> trustContent = (List<String>) contentRecommendations.get("trust");
|
||||
|
||||
if (salesContent != null && !salesContent.isEmpty()) {
|
||||
recommendations.add("Продающий контент: " + String.join(", ", salesContent));
|
||||
}
|
||||
if (expertContent != null && !expertContent.isEmpty()) {
|
||||
recommendations.add("Экспертный контент: " + String.join(", ", expertContent));
|
||||
}
|
||||
if (trustContent != null && !trustContent.isEmpty()) {
|
||||
recommendations.add("Контент для доверия: " + String.join(", ", trustContent));
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем из valueProposition
|
||||
Map<String, Object> valueProposition = (Map<String, Object>) analysisJson.get("valueProposition");
|
||||
if (valueProposition != null) {
|
||||
List<String> mainBenefits = (List<String>) valueProposition.get("mainBenefits");
|
||||
if (mainBenefits != null && !mainBenefits.isEmpty()) {
|
||||
recommendations.add("Основные преимущества для продвижения: " + String.join(", ", mainBenefits));
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем из SWOT - opportunities
|
||||
Map<String, Object> swot = (Map<String, Object>) analysisJson.get("swot");
|
||||
if (swot != null) {
|
||||
List<String> opportunities = (List<String>) swot.get("opportunities");
|
||||
if (opportunities != null && !opportunities.isEmpty()) {
|
||||
for (int i = 0; i < Math.min(3, opportunities.size()); i++) {
|
||||
recommendations.add("Использовать возможность: " + opportunities.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем из positioning
|
||||
Map<String, Object> positioning = (Map<String, Object>) analysisJson.get("positioning");
|
||||
if (positioning != null) {
|
||||
Object keyMessage = positioning.get("keyMessage");
|
||||
if (keyMessage != null) {
|
||||
recommendations.add("Ключевое сообщение: " + keyMessage);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("extractRecommendationsFromJson: Error extracting data: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Если рекомендации пустые, добавляем дефолтные
|
||||
if (recommendations.isEmpty()) {
|
||||
recommendations = Arrays.asList(
|
||||
"Рекомендации доступны в полном маркетинговом отчете",
|
||||
"Изучите раздел с SWOT-анализом для понимания возможностей",
|
||||
"Обратите внимание на рекомендуемые каналы продвижения");
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает стратегию из JSON-анализа.
|
||||
* Заменяет повторный вызов AI для генерации стратегии.
|
||||
*
|
||||
* @param analysisJson JSON с данными маркетингового анализа
|
||||
* @return Map со стратегией (duration, channels, contentTypes)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> extractStrategyFromJson(Map<String, Object> analysisJson) {
|
||||
Map<String, Object> strategy = new HashMap<>();
|
||||
List<String> channels = new ArrayList<>();
|
||||
List<String> contentTypes = new ArrayList<>();
|
||||
String duration = "1 месяц"; // default
|
||||
|
||||
try {
|
||||
// Извлекаем каналы из channels.recommended
|
||||
Map<String, Object> channelsData = (Map<String, Object>) analysisJson.get("channels");
|
||||
if (channelsData != null) {
|
||||
List<Map<String, Object>> recommended = (List<Map<String, Object>>) channelsData.get("recommended");
|
||||
if (recommended != null) {
|
||||
for (Map<String, Object> channel : recommended) {
|
||||
Object channelName = channel.get("name");
|
||||
if (channelName != null) {
|
||||
channels.add(channelName.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем типы контента из contentRecommendations
|
||||
Map<String, Object> contentRecommendations = (Map<String, Object>) analysisJson
|
||||
.get("contentRecommendations");
|
||||
if (contentRecommendations != null) {
|
||||
if (contentRecommendations.containsKey("sales")) {
|
||||
contentTypes.add("продающий контент");
|
||||
}
|
||||
if (contentRecommendations.containsKey("expertise")) {
|
||||
contentTypes.add("экспертный контент");
|
||||
}
|
||||
if (contentRecommendations.containsKey("trust")) {
|
||||
contentTypes.add("доверительный контент");
|
||||
}
|
||||
if (contentRecommendations.containsKey("entertainment")) {
|
||||
contentTypes.add("развлекательный контент");
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем информацию о воронке для определения длительности
|
||||
Map<String, Object> funnel = (Map<String, Object>) analysisJson.get("funnel");
|
||||
if (funnel != null) {
|
||||
List<Map<String, Object>> stages = (List<Map<String, Object>>) funnel.get("stages");
|
||||
if (stages != null && stages.size() > 3) {
|
||||
// Если воронка сложная (более 3 этапов), рекомендуем более длительную кампанию
|
||||
duration = "2-3 месяца";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("extractStrategyFromJson: Error extracting data: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Дефолтные значения если данные не найдены
|
||||
if (channels.isEmpty()) {
|
||||
channels = Arrays.asList("Instagram", "Telegram", "LinkedIn");
|
||||
}
|
||||
if (contentTypes.isEmpty()) {
|
||||
contentTypes = Arrays.asList("посты", "сторис", "видео");
|
||||
}
|
||||
|
||||
strategy.put("duration", duration);
|
||||
strategy.put("channels", channels);
|
||||
strategy.put("contentTypes", contentTypes);
|
||||
|
||||
return strategy;
|
||||
}
|
||||
|
||||
private String buildMarkdownFromReport(Map<String, Object> reportData, MarketingAnalysisRequest request) {
|
||||
logger.info("buildMarkdownFromReport: Starting markdown generation for product: {}", request.getProduct());
|
||||
logger.debug("buildMarkdownFromReport: ReportData keys: {}", reportData != null ? reportData.keySet() : "null");
|
||||
@@ -2861,7 +3102,10 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
}
|
||||
|
||||
// Display full analysis with 10 sections
|
||||
// Display full analysis - это основной блок с полным отчетом
|
||||
// Секции "Целевая аудитория", "Рекомендации", "Стратегия" уже включены в
|
||||
// fullAnalysis
|
||||
// благодаря методу generatePolishedTextReport, поэтому не дублируем их
|
||||
String fullAnalysis = (String) reportData.get("fullAnalysis");
|
||||
if (fullAnalysis != null && !fullAnalysis.trim().isEmpty()) {
|
||||
// Получаем данные для графиков и заменяем плейсхолдеры
|
||||
@@ -2869,7 +3113,7 @@ public class MarketingAnalysisService {
|
||||
Map<String, Object> chartsData = (Map<String, Object>) reportData.get("chartsData");
|
||||
String processedAnalysis = replaceChartPlaceholders(fullAnalysis, chartsData);
|
||||
|
||||
markdown.append("## 📘 Полный маркетинговый анализ\n\n");
|
||||
markdown.append("## Полный маркетинговый анализ\n\n");
|
||||
markdown.append(processedAnalysis).append("\n\n");
|
||||
} else {
|
||||
// Fallback to summary if fullAnalysis is not available
|
||||
@@ -2880,59 +3124,8 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
}
|
||||
|
||||
// Keep backward compatibility sections
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> targetAudience = (Map<String, Object>) reportData.get("targetAudience");
|
||||
if (targetAudience != null) {
|
||||
markdown.append("## Целевая аудитория\n\n");
|
||||
markdown.append(targetAudience.get("description")).append("\n\n");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> channels = (List<String>) targetAudience.get("channels");
|
||||
if (channels != null && !channels.isEmpty()) {
|
||||
markdown.append("### Рекомендуемые каналы:\n\n");
|
||||
for (String channel : channels) {
|
||||
markdown.append("- ").append(channel).append("\n");
|
||||
}
|
||||
markdown.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> recommendations = (List<String>) reportData.get("recommendations");
|
||||
if (recommendations != null && !recommendations.isEmpty()) {
|
||||
markdown.append("## Рекомендации\n\n");
|
||||
for (String rec : recommendations) {
|
||||
markdown.append("- ").append(rec).append("\n");
|
||||
}
|
||||
markdown.append("\n");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> strategy = (Map<String, Object>) reportData.get("strategy");
|
||||
if (strategy != null) {
|
||||
markdown.append("## Маркетинговая стратегия\n\n");
|
||||
markdown.append("### Длительность кампании: ").append(strategy.get("duration")).append("\n\n");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> strategyChannels = (List<String>) strategy.get("channels");
|
||||
if (strategyChannels != null && !strategyChannels.isEmpty()) {
|
||||
markdown.append("### Каналы коммуникации:\n\n");
|
||||
for (String channel : strategyChannels) {
|
||||
markdown.append("- ").append(channel).append("\n");
|
||||
}
|
||||
markdown.append("\n");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> contentTypes = (List<String>) strategy.get("contentTypes");
|
||||
if (contentTypes != null && !contentTypes.isEmpty()) {
|
||||
markdown.append("### Типы контента:\n\n");
|
||||
for (String type : contentTypes) {
|
||||
markdown.append("- ").append(type).append("\n");
|
||||
}
|
||||
markdown.append("\n");
|
||||
}
|
||||
}
|
||||
// Секции targetAudience, recommendations, strategy УДАЛЕНЫ,
|
||||
// так как они уже включены в fullAnalysis и вызывали дублирование
|
||||
|
||||
return markdown.toString();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user