From c3f123aded9b1b2e4e3b2e3f30bc963c75ba570e Mon Sep 17 00:00:00 2001 From: root Date: Thu, 18 Dec 2025 20:01:35 +0500 Subject: [PATCH] . --- .../service/MarketingAnalysisService.java | 605 ++++++++++++------ 1 file changed, 399 insertions(+), 206 deletions(-) diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index 28a0351..a7d7d03 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -1403,6 +1403,89 @@ public class MarketingAnalysisService { } } + /** + * Генерирует профессиональный текстовый маркетинговый отчет на основе + * JSON-анализа. + * Делает ОДИН вызов AI для создания цельного, глубокого отчета. + * + * @param analysisJson JSON с данными маркетингового анализа (включая product, + * businessNiche, goal) + * @return Профессиональный текстовый отчет с плейсхолдерами для графиков + */ + private String generatePolishedTextReport(Map 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 targetAudienceData = request.getTargetAudience(); - if (targetAudienceData != null) { - StringBuilder audienceBuilder = new StringBuilder(); - if (targetAudienceData.containsKey("genders")) { - @SuppressWarnings("unchecked") - List genders = (List) targetAudienceData.get("genders"); - if (genders != null && !genders.isEmpty()) { - audienceBuilder.append("Гендер: ").append(String.join(", ", genders)).append(". "); - } - } - if (targetAudienceData.containsKey("ageRanges")) { - @SuppressWarnings("unchecked") - List ageRanges = (List) targetAudienceData.get("ageRanges"); - if (ageRanges != null && !ageRanges.isEmpty()) { - audienceBuilder.append("Возраст: ").append(String.join(", ", ageRanges)).append(". "); - } - } - if (targetAudienceData.containsKey("types")) { - @SuppressWarnings("unchecked") - List types = (List) 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 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 analysisJson = generateJsonAnalysis(request); @@ -2392,17 +2428,40 @@ public class MarketingAnalysisService { report.put("fullAnalysis", "Не удалось сгенерировать анализ. Пожалуйста, попробуйте позже."); report.put("summary", "Не удалось сгенерировать анализ. Пожалуйста, попробуйте позже."); report.put("chartsData", new HashMap<>()); + + // Добавляем дефолтные значения для обратной совместимости + Map defaultTargetAudience = new HashMap<>(); + defaultTargetAudience.put("description", "Не удалось сгенерировать описание целевой аудитории."); + defaultTargetAudience.put("channels", Arrays.asList("Instagram", "LinkedIn", "Telegram")); + report.put("targetAudience", defaultTargetAudience); + report.put("recommendations", Arrays.asList("Не удалось сгенерировать рекомендации.")); + + Map 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 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 recommendations = extractRecommendationsFromJson(analysisJson); + report.put("recommendations", recommendations); + logger.info("generateMarketingReport: Recommendations extracted. Count: {}", recommendations.size()); + + logger.info("generateMarketingReport: Extracting strategy from analysisJson"); + Map 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 channels = parseChannels(channelsStr); - logger.info("generateMarketingReport: Channels parsed. Count: {}", channels.size()); - - Map 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 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 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 extractTargetAudienceFromJson(Map analysisJson) { + Map targetAudience = new HashMap<>(); + StringBuilder descriptionBuilder = new StringBuilder(); + List channels = new ArrayList<>(); + + try { + // Извлекаем данные из audienceAnalysis + Map audienceAnalysis = (Map) analysisJson.get("audienceAnalysis"); + if (audienceAnalysis != null) { + // Получаем ключевые выводы + List keyTakeaways = (List) 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> segments = (List>) audienceAnalysis.get("segments"); + if (segments != null && !segments.isEmpty()) { + descriptionBuilder.append("Основные сегменты аудитории:\n"); + for (Map 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 primaryChannels = (List) 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 channelsData = (Map) analysisJson.get("channels"); + if (channelsData != null) { + List> recommended = (List>) channelsData.get("recommended"); + if (recommended != null) { + for (Map 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 extractRecommendationsFromJson(Map analysisJson) { + List recommendations = new ArrayList<>(); + + try { + // Извлекаем из contentRecommendations + Map contentRecommendations = (Map) analysisJson + .get("contentRecommendations"); + if (contentRecommendations != null) { + // Собираем рекомендации из всех типов контента + List salesContent = (List) contentRecommendations.get("sales"); + List expertContent = (List) contentRecommendations.get("expertise"); + List trustContent = (List) 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 valueProposition = (Map) analysisJson.get("valueProposition"); + if (valueProposition != null) { + List mainBenefits = (List) valueProposition.get("mainBenefits"); + if (mainBenefits != null && !mainBenefits.isEmpty()) { + recommendations.add("Основные преимущества для продвижения: " + String.join(", ", mainBenefits)); + } + } + + // Извлекаем из SWOT - opportunities + Map swot = (Map) analysisJson.get("swot"); + if (swot != null) { + List opportunities = (List) 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 positioning = (Map) 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 extractStrategyFromJson(Map analysisJson) { + Map strategy = new HashMap<>(); + List channels = new ArrayList<>(); + List contentTypes = new ArrayList<>(); + String duration = "1 месяц"; // default + + try { + // Извлекаем каналы из channels.recommended + Map channelsData = (Map) analysisJson.get("channels"); + if (channelsData != null) { + List> recommended = (List>) channelsData.get("recommended"); + if (recommended != null) { + for (Map channel : recommended) { + Object channelName = channel.get("name"); + if (channelName != null) { + channels.add(channelName.toString()); + } + } + } + } + + // Извлекаем типы контента из contentRecommendations + Map contentRecommendations = (Map) 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 funnel = (Map) analysisJson.get("funnel"); + if (funnel != null) { + List> stages = (List>) 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 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 chartsData = (Map) 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 targetAudience = (Map) reportData.get("targetAudience"); - if (targetAudience != null) { - markdown.append("## Целевая аудитория\n\n"); - markdown.append(targetAudience.get("description")).append("\n\n"); - @SuppressWarnings("unchecked") - List channels = (List) 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 recommendations = (List) 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 strategy = (Map) reportData.get("strategy"); - if (strategy != null) { - markdown.append("## Маркетинговая стратегия\n\n"); - markdown.append("### Длительность кампании: ").append(strategy.get("duration")).append("\n\n"); - - @SuppressWarnings("unchecked") - List strategyChannels = (List) 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 contentTypes = (List) 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(); }