This commit is contained in:
root
2025-12-11 10:55:07 +05:00
parent 8d749cf53c
commit 60f59c66aa
@@ -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()) {