.
This commit is contained in:
@@ -380,8 +380,16 @@ public class MarketingAnalysisService {
|
||||
promptBuilder.append(" - без академического жаргона,\n");
|
||||
promptBuilder.append(" - без лишней теории маркетинга,\n");
|
||||
promptBuilder.append(" - с акцентом на практическую пользу.\n\n");
|
||||
promptBuilder.append("4. Не повторяй сам JSON.\n");
|
||||
promptBuilder.append(" Отчёт — это человеческое объяснение на основе уже рассчитанных данных.\n\n");
|
||||
promptBuilder.append("4. Вставляй специальные метки плейсхолдеров там, где должны быть графики:\n\n");
|
||||
promptBuilder.append(" - [[CHART_SEASONALITY]] — внутри раздела \"III. Анализ рынка и сезонности\"\n");
|
||||
promptBuilder.append(" - [[CHART_AUDIENCE]] — внутри раздела \"IV. Анализ целевой аудитории\"\n");
|
||||
promptBuilder.append(" - [[CHART_COMPETITORS]] — внутри раздела \"V. Анализ конкурентов\"\n");
|
||||
promptBuilder
|
||||
.append(" - [[CHART_CHANNELS]] — внутри раздела \"VII. Каналы продвижения и их потенциал\"\n");
|
||||
promptBuilder.append(" - [[CHART_FUNNEL]] — внутри раздела \"VIII. Воронка спроса\"\n");
|
||||
promptBuilder.append(" - [[CHART_SWOT]] — внутри раздела \"VI. SWOT-анализ\"\n\n");
|
||||
promptBuilder.append(
|
||||
" Вставляй метки в логичных местах текста, где график будет уместен для иллюстрации данных.\n\n");
|
||||
promptBuilder.append("5. Не выдумывай новые цифры — используй только те, что есть в JSON.\n");
|
||||
|
||||
String prompt = promptBuilder.toString();
|
||||
@@ -1081,79 +1089,6 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
markdown.append("\n");
|
||||
|
||||
// Add chart data tables
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> chartsData = (Map<String, Object>) reportData.get("chartsData");
|
||||
if (chartsData != null && !chartsData.isEmpty()) {
|
||||
markdown.append("## 📊 Числовые данные для графиков\n\n");
|
||||
|
||||
// Seasonality JSON block
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Integer> seasonality = (Map<String, Integer>) chartsData.get("seasonality");
|
||||
appendJsonChart(markdown, "### 1️⃣ Сезонность спроса (12 месяцев)", "seasonality", seasonality);
|
||||
|
||||
// Competitors JSON block
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> competitors = (List<Map<String, Object>>) chartsData.get("competitors");
|
||||
appendJsonChart(markdown, "### 2️⃣ Сравнительный анализ конкурентов", "competitors", competitors);
|
||||
|
||||
// Audience segmentation JSON block
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> audienceSegmentation = (Map<String, Object>) chartsData.get("audienceSegmentation");
|
||||
appendJsonChart(markdown, "### 3️⃣ Сегментация целевой аудитории", "audienceSegmentation",
|
||||
audienceSegmentation);
|
||||
|
||||
// Channels potential JSON block
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> channelsPotential = (List<Map<String, Object>>) chartsData
|
||||
.get("channelsPotential");
|
||||
appendJsonChart(markdown, "### 4️⃣ Потенциал каналов продвижения", "channelsPotential", channelsPotential);
|
||||
|
||||
// Conversion funnel JSON block
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> conversionFunnel = (List<Map<String, Object>>) chartsData.get("conversionFunnel");
|
||||
appendJsonChart(markdown, "### 5️⃣ Конверсионная воронка", "funnel", conversionFunnel);
|
||||
|
||||
// SWOT table (if available from JSON)
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> swot = (Map<String, Object>) chartsData.get("swot");
|
||||
if (swot != null) {
|
||||
markdown.append("### 6️⃣ SWOT-анализ\n\n");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> strengths = (List<String>) swot.get("strengths");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> weaknesses = (List<String>) swot.get("weaknesses");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> opportunities = (List<String>) swot.get("opportunities");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> threats = (List<String>) swot.get("threats");
|
||||
|
||||
markdown.append("| Сильные стороны | Слабые стороны |\n");
|
||||
markdown.append("|----------------|----------------|\n");
|
||||
markdown.append("| ");
|
||||
if (strengths != null && !strengths.isEmpty()) {
|
||||
markdown.append(String.join("; ", strengths));
|
||||
}
|
||||
markdown.append(" | ");
|
||||
if (weaknesses != null && !weaknesses.isEmpty()) {
|
||||
markdown.append(String.join("; ", weaknesses));
|
||||
}
|
||||
markdown.append(" |\n\n");
|
||||
|
||||
markdown.append("| Возможности | Угрозы |\n");
|
||||
markdown.append("|-------------|-------|\n");
|
||||
markdown.append("| ");
|
||||
if (opportunities != null && !opportunities.isEmpty()) {
|
||||
markdown.append(String.join("; ", opportunities));
|
||||
}
|
||||
markdown.append(" | ");
|
||||
if (threats != null && !threats.isEmpty()) {
|
||||
markdown.append(String.join("; ", threats));
|
||||
}
|
||||
markdown.append(" |\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Add additional sections from JSON analysis if available
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> analysisJson = (Map<String, Object>) reportData.get("analysisJson");
|
||||
@@ -1256,8 +1191,13 @@ public class MarketingAnalysisService {
|
||||
// Display full analysis with 10 sections
|
||||
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);
|
||||
|
||||
markdown.append("## 📘 Полный маркетинговый анализ\n\n");
|
||||
markdown.append(fullAnalysis).append("\n\n");
|
||||
markdown.append(processedAnalysis).append("\n\n");
|
||||
} else {
|
||||
// Fallback to summary if fullAnalysis is not available
|
||||
String summary = (String) reportData.get("summary");
|
||||
@@ -1352,6 +1292,157 @@ public class MarketingAnalysisService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Заменяет плейсхолдеры в тексте отчета на JSON-блоки с данными графиков
|
||||
*
|
||||
* @param fullAnalysis текст отчета с плейсхолдерами
|
||||
* @param chartsData данные для графиков
|
||||
* @return текст отчета с замененными плейсхолдерами на JSON-блоки
|
||||
*/
|
||||
private String replaceChartPlaceholders(String fullAnalysis, Map<String, Object> chartsData) {
|
||||
if (fullAnalysis == null || fullAnalysis.trim().isEmpty()) {
|
||||
return fullAnalysis;
|
||||
}
|
||||
|
||||
if (chartsData == null || chartsData.isEmpty()) {
|
||||
// Если данных нет, просто удаляем все плейсхолдеры
|
||||
return fullAnalysis
|
||||
.replace("[[CHART_SEASONALITY]]", "")
|
||||
.replace("[[CHART_AUDIENCE]]", "")
|
||||
.replace("[[CHART_COMPETITORS]]", "")
|
||||
.replace("[[CHART_CHANNELS]]", "")
|
||||
.replace("[[CHART_FUNNEL]]", "")
|
||||
.replace("[[CHART_SWOT]]", "");
|
||||
}
|
||||
|
||||
String result = fullAnalysis;
|
||||
Set<String> processedPlaceholders = new HashSet<>();
|
||||
|
||||
// Маппинг плейсхолдеров на ключи данных и chartKey
|
||||
Map<String, ChartMapping> placeholderMap = new HashMap<>();
|
||||
placeholderMap.put("[[CHART_SEASONALITY]]", new ChartMapping("seasonality", "seasonality"));
|
||||
placeholderMap.put("[[CHART_AUDIENCE]]", new ChartMapping("audienceSegmentation", "audienceSegmentation"));
|
||||
placeholderMap.put("[[CHART_COMPETITORS]]", new ChartMapping("competitors", "competitors"));
|
||||
placeholderMap.put("[[CHART_CHANNELS]]", new ChartMapping("channelsPotential", "channelsPotential"));
|
||||
placeholderMap.put("[[CHART_FUNNEL]]", new ChartMapping("conversionFunnel", "funnel"));
|
||||
placeholderMap.put("[[CHART_SWOT]]", new ChartMapping("swot", "swot"));
|
||||
|
||||
// Заменяем найденные плейсхолдеры
|
||||
for (Map.Entry<String, ChartMapping> entry : placeholderMap.entrySet()) {
|
||||
String placeholder = entry.getKey();
|
||||
ChartMapping mapping = entry.getValue();
|
||||
|
||||
if (result.contains(placeholder)) {
|
||||
processedPlaceholders.add(placeholder);
|
||||
Object chartData = chartsData.get(mapping.dataKey);
|
||||
|
||||
if (!isEmptyChartData(chartData)) {
|
||||
try {
|
||||
String jsonStr = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(chartData);
|
||||
String jsonBlock = "\n```json:" + mapping.chartKey + "\n" + jsonStr + "\n```\n";
|
||||
result = result.replace(placeholder, jsonBlock);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn("Failed to serialize chart data for {}: {}", mapping.dataKey, e.getMessage());
|
||||
// Удаляем плейсхолдер при ошибке сериализации
|
||||
result = result.replace(placeholder, "");
|
||||
}
|
||||
} else {
|
||||
// Данных нет - удаляем плейсхолдер
|
||||
result = result.replace(placeholder, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: добавляем графики в конец соответствующих разделов, если
|
||||
// плейсхолдер отсутствовал
|
||||
result = addMissingChartsToSections(result, chartsData, placeholderMap, processedPlaceholders);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Вспомогательный класс для маппинга плейсхолдеров
|
||||
*/
|
||||
private static class ChartMapping {
|
||||
final String dataKey;
|
||||
final String chartKey;
|
||||
|
||||
ChartMapping(String dataKey, String chartKey) {
|
||||
this.dataKey = dataKey;
|
||||
this.chartKey = chartKey;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет графики в конец соответствующих разделов, если плейсхолдер
|
||||
* отсутствовал, но данные есть
|
||||
*/
|
||||
private String addMissingChartsToSections(String text, Map<String, Object> chartsData,
|
||||
Map<String, ChartMapping> placeholderMap, Set<String> processedPlaceholders) {
|
||||
String result = text;
|
||||
|
||||
// Маппинг разделов на плейсхолдеры
|
||||
Map<String, String> sectionToPlaceholder = new HashMap<>();
|
||||
sectionToPlaceholder.put("III\\.\\s*Анализ рынка и сезонности", "[[CHART_SEASONALITY]]");
|
||||
sectionToPlaceholder.put("IV\\.\\s*Анализ целевой аудитории", "[[CHART_AUDIENCE]]");
|
||||
sectionToPlaceholder.put("V\\.\\s*Анализ конкурентов", "[[CHART_COMPETITORS]]");
|
||||
sectionToPlaceholder.put("VII\\.\\s*Каналы продвижения", "[[CHART_CHANNELS]]");
|
||||
sectionToPlaceholder.put("VIII\\.\\s*Воронка спроса", "[[CHART_FUNNEL]]");
|
||||
sectionToPlaceholder.put("VI\\.\\s*SWOT-анализ", "[[CHART_SWOT]]");
|
||||
|
||||
for (Map.Entry<String, String> sectionEntry : sectionToPlaceholder.entrySet()) {
|
||||
String sectionPattern = sectionEntry.getKey();
|
||||
String placeholder = sectionEntry.getValue();
|
||||
|
||||
// Если плейсхолдер уже был обработан, пропускаем
|
||||
if (processedPlaceholders.contains(placeholder)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ChartMapping mapping = placeholderMap.get(placeholder);
|
||||
if (mapping == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object chartData = chartsData.get(mapping.dataKey);
|
||||
if (isEmptyChartData(chartData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ищем раздел в тексте
|
||||
java.util.regex.Pattern pattern = java.util.regex.Pattern
|
||||
.compile("(##+\\s*" + sectionPattern + "[^#]*)", java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||
java.util.regex.Matcher matcher = pattern.matcher(result);
|
||||
|
||||
if (matcher.find()) {
|
||||
try {
|
||||
String jsonStr = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(chartData);
|
||||
String jsonBlock = "\n\n```json:" + mapping.chartKey + "\n" + jsonStr + "\n```\n";
|
||||
// Вставляем после найденного раздела, перед следующим разделом или концом
|
||||
// текста
|
||||
int insertPos = matcher.end();
|
||||
// Ищем конец текущего раздела (начало следующего раздела или конец текста)
|
||||
String remainingText = result.substring(insertPos);
|
||||
java.util.regex.Pattern nextSectionPattern = java.util.regex.Pattern.compile("^##+\\s*[IVX]+\\.");
|
||||
java.util.regex.Matcher nextSectionMatcher = nextSectionPattern.matcher(remainingText);
|
||||
|
||||
if (nextSectionMatcher.find()) {
|
||||
// Вставляем перед следующим разделом
|
||||
int nextSectionPos = insertPos + nextSectionMatcher.start();
|
||||
result = result.substring(0, nextSectionPos) + jsonBlock + result.substring(nextSectionPos);
|
||||
} else {
|
||||
// Вставляем в конец текста
|
||||
result = result + jsonBlock;
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn("Failed to serialize chart data for fallback {}: {}", mapping.dataKey, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public MarketingAnalysisResult getAnalysisResult(String analysisId) {
|
||||
Optional<MarketingAnalysis> optAnalysis = repository.findById(analysisId);
|
||||
if (optAnalysis.isEmpty()) {
|
||||
|
||||
Reference in New Issue
Block a user