From c79ace9d411ea9583b819ec515852949716e62bb Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Dec 2025 00:37:18 +0500 Subject: [PATCH] . --- .../service/MarketingAnalysisService.java | 336 ++++++++++-------- 1 file changed, 187 insertions(+), 149 deletions(-) diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index 1f65d44..9e1b201 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -2126,143 +2126,6 @@ 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("Пиши как высокооплачиваемый бизнес-консультант. Избегай воды, используй цифры из JSON, "); - promptBuilder.append( - "делай глубокие сравнения нашего бренда с конкурентами (используй данные из competitorAnalysis и comparisonTable). "); - promptBuilder.append("Каждое утверждение должно быть подкреплено конкретными данными из JSON.\n\n"); - - promptBuilder.append("ТРЕБОВАНИЯ К ОБЪЕМУ ОТЧЕТА:\n"); - promptBuilder.append("1. Напиши подробный отчет объемом не менее 2500-3000 слов\n"); - promptBuilder.append( - "2. КРИТИЧНО: Каждый из 11 разделов (I-XI) должен содержать минимум 5-6 развернутых абзацев с глубокой аналитикой\n"); - promptBuilder - .append("3. Каждый абзац должен быть содержательным и развернутым, не менее 4-5 предложений\n\n"); - - promptBuilder.append("ТРЕБОВАНИЯ К ОТЧЕТУ:\n"); - promptBuilder.append("1. Используй деловой стиль письма, убедительный для владельца бизнеса\n"); - promptBuilder.append("2. Добавляй аналитические инсайты и практические выводы\n"); - promptBuilder.append("3. Структурируй текст по разделам с римскими цифрами (I, II, III и т.д.)\n"); - promptBuilder.append("4. Используй данные ТОЛЬКО из переданного JSON, не выдумывай цифры\n"); - promptBuilder.append("5. Пиши конкретно и практично, избегай общих фраз\n"); - promptBuilder.append( - "6. Обязательно используй данные из полей product, businessNiche, goal, regions для контекста анализа\n\n"); - - promptBuilder.append("АНАЛИТИЧЕСКАЯ ГЛУБИНА:\n"); - promptBuilder.append("1. Используй \"Метод Case Study\" и \"Бенчмаркинг\" для глубокого анализа\n"); - promptBuilder - .append("2. НЕ просто перечисляй данные из JSON, а ИНТЕРПРЕТИРУЙ их с практическими выводами\n"); - promptBuilder.append( - "3. Приводи конкретные примеры: если сезонность падает в июле, объясни, как именно это влияет на кэшфлоу бизнеса и предложи конкретные акции для перекрытия кассового разрыва\n"); - promptBuilder.append( - "4. Каждое утверждение должно быть подкреплено конкретными данными из JSON с объяснением их бизнес-значения\n"); - promptBuilder.append( - "5. Делай глубокие сравнения нашего бренда с конкурентами (используй данные из competitorAnalysis и comparisonTable)\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( - "1. Ты ОБЯЗАН вставить каждый плейсхолдер [[CHART_...]] в контекст обсуждения соответствующего раздела\n"); - promptBuilder.append( - "2. Обязательно расставь ВСЕ плейсхолдеры графиков: [[CHART_SEASONALITY]], [[CHART_AUDIENCE]], "); - promptBuilder.append("[[CHART_COMPETITORS]], [[CHART_SWOT]], [[CHART_CHANNELS]], [[CHART_FUNNEL]]\n"); - promptBuilder.append( - "3. Каждый плейсхолдер должен быть на отдельной строке в соответствующем разделе, как указано в структуре выше\n"); - promptBuilder.append( - "4. ПЕРЕД каждым графиком напиши вводный абзац (2-3 предложения), начинающийся с: \"Анализ данных на графике ниже показывает...\"\n"); - promptBuilder.append( - "5. ПОСЛЕ каждого графика напиши интерпретацию данных (2-3 предложения), начинающуюся с: \"Из этих показателей следует, что...\"\n"); - promptBuilder.append( - "6. НЕ пропускай ни одного плейсхолдера! Каждый график должен быть встроен в повествование раздела\n\n"); - - promptBuilder.append("ТЕМЫ ДЛЯ РАСШИРЕНИЯ В РАЗДЕЛАХ:\n"); - promptBuilder.append( - "1. В разделе VI (SWOT-анализ): обязательно проведи анализ пересечений - объясни, как сильные стороны помогут использовать возможности, и как слабые стороны могут помешать противостоять угрозам\n"); - promptBuilder.append( - "2. В разделе IV (Анализ целевой аудитории): для каждого сегмента опиши \"путь клиента\" (Customer Journey Map) - этапы от первого контакта до покупки и лояльности\n"); - promptBuilder.append( - "3. В разделе V (Анализ конкурентов): проведи детальный разбор их контент-воронок - какие типы контента они используют на каждом этапе воронки, какие каналы задействуют, какие месседжи доносят\n\n"); - - promptBuilder.append("ВАЖНО:\n"); - promptBuilder.append("- Плейсхолдеры [[CHART_...]] вставляй ТОЧНО как указано выше, на отдельной строке\n"); - promptBuilder.append("- Начинай сразу с 'I. Краткое резюме', без вступлений\n"); - promptBuilder.append( - "- НЕ добавляй секции 'Целевая аудитория', 'Рекомендации', 'Стратегия' отдельно в конце — они уже включены в структуру\n"); - promptBuilder.append("- Пиши так, чтобы отчет был полезен для принятия бизнес-решений\n"); - promptBuilder.append( - "- Цель: получить полноценное маркетинговое исследование на 15+ страниц с глубокой аналитикой и визуализациями\n\n"); - - promptBuilder.append("JSON ДАННЫЕ:\n"); - promptBuilder.append(jsonString); - - String prompt = promptBuilder.toString(); - logger.info("generatePolishedTextReport: Calling AI for text report generation with model: {}", - textModelName); - - String result = openAIAnalyticsService.generateWithInstructionWithModel(jsonString, prompt, "ru", - textModelName); - - 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. Анализ рынка и @@ -3222,10 +3085,190 @@ public class MarketingAnalysisService { // Сохраняем JSON в отчёт report.put("analysisJson", analysisJson); - // Шаг 2: Генерация профессионального текстового отчёта (ОДИН вызов AI) - logger.info("generateMarketingReport: Starting polished text report generation"); - String textReport = generatePolishedTextReport(analysisJson); - logger.info("generateMarketingReport: Polished text report generated. Length: {} chars", + // Шаг 2: Генерация профессионального текстового отчёта (11 параллельных вызовов + // AI) + logger.info("generateMarketingReport: Starting modular text report generation (11 sections)"); + + // Создаем CompletableFuture для каждого раздела + CompletableFuture section1 = CompletableFuture + .supplyAsync(() -> genSectionSummary(prepareSectionContext(analysisJson, "summary"))); + CompletableFuture section2 = CompletableFuture + .supplyAsync(() -> genSectionProduct(prepareSectionContext(analysisJson, "product"))); + CompletableFuture section3 = CompletableFuture + .supplyAsync(() -> genSectionMarket(prepareSectionContext(analysisJson, "market"))); + CompletableFuture section4 = CompletableFuture + .supplyAsync(() -> genSectionAudience(prepareSectionContext(analysisJson, "audience"))); + CompletableFuture section5 = CompletableFuture + .supplyAsync(() -> genSectionCompetitors(prepareSectionContext(analysisJson, "competitors"))); + CompletableFuture section6 = CompletableFuture + .supplyAsync(() -> genSectionSWOT(prepareSectionContext(analysisJson, "swot"))); + CompletableFuture section7 = CompletableFuture + .supplyAsync(() -> genSectionChannels(prepareSectionContext(analysisJson, "channels"))); + CompletableFuture section8 = CompletableFuture + .supplyAsync(() -> genSectionFunnel(prepareSectionContext(analysisJson, "funnel"))); + CompletableFuture section9 = CompletableFuture + .supplyAsync(() -> genSectionPositioning(prepareSectionContext(analysisJson, "positioning"))); + CompletableFuture section10 = CompletableFuture + .supplyAsync(() -> genSectionContent(prepareSectionContext(analysisJson, "content"))); + CompletableFuture section11 = CompletableFuture + .supplyAsync(() -> genSectionStrategy(prepareSectionContext(analysisJson, "strategy"))); + + // Ждем завершения всех разделов + CompletableFuture.allOf(section1, section2, section3, section4, section5, section6, + section7, section8, section9, section10, section11).join(); + + // Собираем результаты в правильном порядке (I → XI) + StringBuilder fullReportBuilder = new StringBuilder(); + try { + String s1 = section1.get(); + if (s1 != null && !s1.trim().isEmpty()) { + fullReportBuilder.append(s1).append("\n\n"); + } else { + logger.warn("generateMarketingReport: Section I (Summary) failed to generate"); + fullReportBuilder.append("I. Краткое резюме\n\nНе удалось сгенерировать раздел.\n\n"); + } + } catch (Exception e) { + logger.error("generateMarketingReport: Error getting section I: {}", e.getMessage(), e); + fullReportBuilder.append("I. Краткое резюме\n\nОшибка при генерации раздела.\n\n"); + } + + try { + String s2 = section2.get(); + if (s2 != null && !s2.trim().isEmpty()) { + fullReportBuilder.append(s2).append("\n\n"); + } else { + logger.warn("generateMarketingReport: Section II (Product) failed to generate"); + fullReportBuilder.append("II. Анализ продукта\n\nНе удалось сгенерировать раздел.\n\n"); + } + } catch (Exception e) { + logger.error("generateMarketingReport: Error getting section II: {}", e.getMessage(), e); + fullReportBuilder.append("II. Анализ продукта\n\nОшибка при генерации раздела.\n\n"); + } + + try { + String s3 = section3.get(); + if (s3 != null && !s3.trim().isEmpty()) { + fullReportBuilder.append(s3).append("\n\n"); + } else { + logger.warn("generateMarketingReport: Section III (Market) failed to generate"); + fullReportBuilder.append("III. Анализ рынка и сезонности\n\nНе удалось сгенерировать раздел.\n\n"); + } + } catch (Exception e) { + logger.error("generateMarketingReport: Error getting section III: {}", e.getMessage(), e); + fullReportBuilder.append("III. Анализ рынка и сезонности\n\nОшибка при генерации раздела.\n\n"); + } + + try { + String s4 = section4.get(); + if (s4 != null && !s4.trim().isEmpty()) { + fullReportBuilder.append(s4).append("\n\n"); + } else { + logger.warn("generateMarketingReport: Section IV (Audience) failed to generate"); + fullReportBuilder.append("IV. Анализ целевой аудитории\n\nНе удалось сгенерировать раздел.\n\n"); + } + } catch (Exception e) { + logger.error("generateMarketingReport: Error getting section IV: {}", e.getMessage(), e); + fullReportBuilder.append("IV. Анализ целевой аудитории\n\nОшибка при генерации раздела.\n\n"); + } + + try { + String s5 = section5.get(); + if (s5 != null && !s5.trim().isEmpty()) { + fullReportBuilder.append(s5).append("\n\n"); + } else { + logger.warn("generateMarketingReport: Section V (Competitors) failed to generate"); + fullReportBuilder.append("V. Анализ конкурентов\n\nНе удалось сгенерировать раздел.\n\n"); + } + } catch (Exception e) { + logger.error("generateMarketingReport: Error getting section V: {}", e.getMessage(), e); + fullReportBuilder.append("V. Анализ конкурентов\n\nОшибка при генерации раздела.\n\n"); + } + + try { + String s6 = section6.get(); + if (s6 != null && !s6.trim().isEmpty()) { + fullReportBuilder.append(s6).append("\n\n"); + } else { + logger.warn("generateMarketingReport: Section VI (SWOT) failed to generate"); + fullReportBuilder.append("VI. SWOT-анализ\n\nНе удалось сгенерировать раздел.\n\n"); + } + } catch (Exception e) { + logger.error("generateMarketingReport: Error getting section VI: {}", e.getMessage(), e); + fullReportBuilder.append("VI. SWOT-анализ\n\nОшибка при генерации раздела.\n\n"); + } + + try { + String s7 = section7.get(); + if (s7 != null && !s7.trim().isEmpty()) { + fullReportBuilder.append(s7).append("\n\n"); + } else { + logger.warn("generateMarketingReport: Section VII (Channels) failed to generate"); + fullReportBuilder + .append("VII. Каналы продвижения и их потенциал\n\nНе удалось сгенерировать раздел.\n\n"); + } + } catch (Exception e) { + logger.error("generateMarketingReport: Error getting section VII: {}", e.getMessage(), e); + fullReportBuilder.append("VII. Каналы продвижения и их потенциал\n\nОшибка при генерации раздела.\n\n"); + } + + try { + String s8 = section8.get(); + if (s8 != null && !s8.trim().isEmpty()) { + fullReportBuilder.append(s8).append("\n\n"); + } else { + logger.warn("generateMarketingReport: Section VIII (Funnel) failed to generate"); + fullReportBuilder.append("VIII. Воронка спроса\n\nНе удалось сгенерировать раздел.\n\n"); + } + } catch (Exception e) { + logger.error("generateMarketingReport: Error getting section VIII: {}", e.getMessage(), e); + fullReportBuilder.append("VIII. Воронка спроса\n\nОшибка при генерации раздела.\n\n"); + } + + try { + String s9 = section9.get(); + if (s9 != null && !s9.trim().isEmpty()) { + fullReportBuilder.append(s9).append("\n\n"); + } else { + logger.warn("generateMarketingReport: Section IX (Positioning) failed to generate"); + fullReportBuilder.append( + "IX. Рекомендации по позиционированию и УТП\n\nНе удалось сгенерировать раздел.\n\n"); + } + } catch (Exception e) { + logger.error("generateMarketingReport: Error getting section IX: {}", e.getMessage(), e); + fullReportBuilder + .append("IX. Рекомендации по позиционированию и УТП\n\nОшибка при генерации раздела.\n\n"); + } + + try { + String s10 = section10.get(); + if (s10 != null && !s10.trim().isEmpty()) { + fullReportBuilder.append(s10).append("\n\n"); + } else { + logger.warn("generateMarketingReport: Section X (Content) failed to generate"); + fullReportBuilder.append("X. Рекомендации по контенту\n\nНе удалось сгенерировать раздел.\n\n"); + } + } catch (Exception e) { + logger.error("generateMarketingReport: Error getting section X: {}", e.getMessage(), e); + fullReportBuilder.append("X. Рекомендации по контенту\n\nОшибка при генерации раздела.\n\n"); + } + + try { + String s11 = section11.get(); + if (s11 != null && !s11.trim().isEmpty()) { + fullReportBuilder.append(s11); + } else { + logger.warn("generateMarketingReport: Section XI (Strategy) failed to generate"); + fullReportBuilder.append( + "XI. Итоговая стратегия: что делать в первую очередь\n\nНе удалось сгенерировать раздел."); + } + } catch (Exception e) { + logger.error("generateMarketingReport: Error getting section XI: {}", e.getMessage(), e); + fullReportBuilder + .append("XI. Итоговая стратегия: что делать в первую очередь\n\nОшибка при генерации раздела."); + } + + String textReport = fullReportBuilder.toString(); + logger.info("generateMarketingReport: Modular text report generated. Length: {} chars", textReport != null ? textReport.length() : 0); // Извлекаем данные для графиков из JSON @@ -3463,18 +3506,13 @@ public class MarketingAnalysisService { markdown.append("\n"); // Display full analysis - это основной блок с полным отчетом - // Все секции (I-XI) уже включены в fullAnalysis благодаря - // generatePolishedTextReport + // Все секции (I-XI) уже включены в fullAnalysis благодаря модульной генерации + // Плейсхолдеры графиков уже заменены в generateMarketingReport String fullAnalysis = (String) reportData.get("fullAnalysis"); if (fullAnalysis != null && !fullAnalysis.trim().isEmpty()) { - // Получаем данные для графиков и заменяем плейсхолдеры - @SuppressWarnings("unchecked") - Map chartsData = (Map) reportData.get("chartsData"); - String processedAnalysis = replaceChartPlaceholders(fullAnalysis, chartsData); - // Добавляем полный отчет без дополнительного заголовка, // так как fullAnalysis уже начинается с "I. Краткое резюме" - markdown.append(processedAnalysis).append("\n\n"); + markdown.append(fullAnalysis).append("\n\n"); } else { // Если fullAnalysis пустой, выводим сообщение об ошибке markdown.append("Не удалось сгенерировать анализ. Пожалуйста, попробуйте позже.\n\n");