This commit is contained in:
root
2025-12-18 20:01:35 +05:00
parent 7ade14c2ee
commit c3f123aded
@@ -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();
}