This commit is contained in:
root
2025-12-19 00:37:18 +05:00
parent 1422d7ddc1
commit c79ace9d41
@@ -2126,143 +2126,6 @@ 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("Пиши как высокооплачиваемый бизнес-консультант. Избегай воды, используй цифры из 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<String> section1 = CompletableFuture
.supplyAsync(() -> genSectionSummary(prepareSectionContext(analysisJson, "summary")));
CompletableFuture<String> section2 = CompletableFuture
.supplyAsync(() -> genSectionProduct(prepareSectionContext(analysisJson, "product")));
CompletableFuture<String> section3 = CompletableFuture
.supplyAsync(() -> genSectionMarket(prepareSectionContext(analysisJson, "market")));
CompletableFuture<String> section4 = CompletableFuture
.supplyAsync(() -> genSectionAudience(prepareSectionContext(analysisJson, "audience")));
CompletableFuture<String> section5 = CompletableFuture
.supplyAsync(() -> genSectionCompetitors(prepareSectionContext(analysisJson, "competitors")));
CompletableFuture<String> section6 = CompletableFuture
.supplyAsync(() -> genSectionSWOT(prepareSectionContext(analysisJson, "swot")));
CompletableFuture<String> section7 = CompletableFuture
.supplyAsync(() -> genSectionChannels(prepareSectionContext(analysisJson, "channels")));
CompletableFuture<String> section8 = CompletableFuture
.supplyAsync(() -> genSectionFunnel(prepareSectionContext(analysisJson, "funnel")));
CompletableFuture<String> section9 = CompletableFuture
.supplyAsync(() -> genSectionPositioning(prepareSectionContext(analysisJson, "positioning")));
CompletableFuture<String> section10 = CompletableFuture
.supplyAsync(() -> genSectionContent(prepareSectionContext(analysisJson, "content")));
CompletableFuture<String> 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<String, Object> chartsData = (Map<String, Object>) 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");