This commit is contained in:
root
2025-12-20 00:09:40 +05:00
parent 47976a39cf
commit 3e56f565dc
@@ -1019,11 +1019,11 @@ public class MarketingAnalysisService {
promptBuilder.append(" XI. Итоговая стратегия: что делать в первую очередь\n\n");
promptBuilder.append("2. Используй данные ТОЛЬКО из JSON:\n\n");
promptBuilder.append(" - seasonality → для описания сезонности\n");
promptBuilder.append(" - audienceSegments → для портрета ЦА\n");
promptBuilder.append(" - competitors → для анализа конкурентов\n");
promptBuilder.append(" - channels → для описания каналов\n");
promptBuilder.append(" - funnel → для объяснения этапов воронки\n");
promptBuilder.append(" - swot → для SWOT-раздела\n");
promptBuilder.append(" - market → для объёма рынка, темпов роста, трендов\n");
promptBuilder.append(" - audienceAnalysis → для портрета ЦА (segments, ageGroups, genderDistribution)\n");
promptBuilder
.append(" - competitorAnalysis → для анализа конкурентов (marketShareChart, comparisonTable)\n");
promptBuilder.append(" - strategyAnalysis → для воронки, каналов и SWOT (если присутствует)\n");
promptBuilder.append(" - valueProposition, positioning, contentRecommendations → для рекомендаций.\n\n");
promptBuilder.append("3. Пиши простым, деловым, понятным языком:\n\n");
promptBuilder.append(" - без академического жаргона,\n");
@@ -1031,8 +1031,11 @@ public class MarketingAnalysisService {
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_AGE_DISTRIBUTION]] — внутри раздела \"IV. Анализ целевой аудитории\"\n");
promptBuilder
.append(" - [[CHART_GENDER_DISTRIBUTION]] — внутри раздела \"IV. Анализ целевой аудитории\"\n");
promptBuilder.append(" - [[CHART_MARKET_SHARE]] — внутри раздела \"V. Анализ конкурентов\"\n");
promptBuilder.append(" - [[TABLE_COMPARISON]] — внутри раздела \"V. Анализ конкурентов\"\n");
promptBuilder
.append(" - [[CHART_CHANNELS]] — внутри раздела \"VII. Каналы продвижения и их потенциал\"\n");
promptBuilder.append(" - [[CHART_FUNNEL]] — внутри раздела \"VIII. Воронка спроса\"\n");
@@ -1179,7 +1182,8 @@ public class MarketingAnalysisService {
fullReport.append("\n");
}
}
fullReport.append("[[CHART_AUDIENCE]]\n\n");
fullReport.append("[[CHART_AGE_DISTRIBUTION]]\n\n");
fullReport.append("[[CHART_GENDER_DISTRIBUTION]]\n\n");
}
// V. Анализ конкурентов
@@ -1233,7 +1237,8 @@ public class MarketingAnalysisService {
fullReport.append("\n");
}
}
fullReport.append("[[CHART_COMPETITORS]]\n\n");
fullReport.append("[[CHART_MARKET_SHARE]]\n\n");
fullReport.append("[[TABLE_COMPARISON]]\n\n");
}
// VI. SWOT-анализ
@@ -1563,22 +1568,34 @@ public class MarketingAnalysisService {
}
break;
case "audience":
if (fullJson.containsKey("audienceSegments")) {
// Новая структура: audienceAnalysis (ageGroups, genderDistribution, segments,
// keyTakeaways, etc.)
if (fullJson.containsKey("audienceAnalysis")) {
context.put("audienceAnalysis", fullJson.get("audienceAnalysis"));
} else if (fullJson.containsKey("audienceSegments")) {
// Backward compatibility (старая структура)
context.put("audienceSegments", fullJson.get("audienceSegments"));
}
break;
case "competitors":
if (fullJson.containsKey("competitors")) {
context.put("competitors", fullJson.get("competitors"));
}
if (fullJson.containsKey("comparisonTable")) {
context.put("comparisonTable", fullJson.get("comparisonTable"));
}
if (fullJson.containsKey("swotCompetitors")) {
context.put("swotCompetitors", fullJson.get("swotCompetitors"));
}
if (fullJson.containsKey("marketShareChart")) {
context.put("marketShareChart", fullJson.get("marketShareChart"));
// Новая структура: competitorAnalysis (marketShareChart, comparisonTable,
// competitorsDetails, swotCompetitors, etc.)
if (fullJson.containsKey("competitorAnalysis")) {
context.put("competitorAnalysis", fullJson.get("competitorAnalysis"));
} else {
// Backward compatibility (старая структура)
if (fullJson.containsKey("competitors")) {
context.put("competitors", fullJson.get("competitors"));
}
if (fullJson.containsKey("comparisonTable")) {
context.put("comparisonTable", fullJson.get("comparisonTable"));
}
if (fullJson.containsKey("swotCompetitors")) {
context.put("swotCompetitors", fullJson.get("swotCompetitors"));
}
if (fullJson.containsKey("marketShareChart")) {
context.put("marketShareChart", fullJson.get("marketShareChart"));
}
}
break;
case "swot":
@@ -1617,15 +1634,23 @@ public class MarketingAnalysisService {
break;
case "strategy":
// Для итоговой стратегии включаем все ключевые данные
if (fullJson.containsKey("swot")) {
context.put("swot", fullJson.get("swot"));
}
if (fullJson.containsKey("channels")) {
context.put("channels", fullJson.get("channels"));
}
if (fullJson.containsKey("funnel")) {
context.put("funnel", fullJson.get("funnel"));
// Новая структура: strategyAnalysis (funnel, channelsPotential, globalSwot,
// etc.)
if (fullJson.containsKey("strategyAnalysis")) {
context.put("strategyAnalysis", fullJson.get("strategyAnalysis"));
} else {
// Backward compatibility (старая структура)
if (fullJson.containsKey("swot")) {
context.put("swot", fullJson.get("swot"));
}
if (fullJson.containsKey("channels")) {
context.put("channels", fullJson.get("channels"));
}
if (fullJson.containsKey("funnel")) {
context.put("funnel", fullJson.get("funnel"));
}
}
// Дополнительные "склейки" стратегии (небольшие поля)
if (fullJson.containsKey("positioning")) {
context.put("positioning", fullJson.get("positioning"));
}
@@ -1655,7 +1680,11 @@ public class MarketingAnalysisService {
promptBuilder.append("1. Объем: минимум 500-700 слов\n");
promptBuilder.append("2. Стиль: деловой, убедительный для владельца бизнеса\n");
promptBuilder.append("3. Глубина: аналитические инсайты с практическими выводами\n");
promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n\n");
promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n");
promptBuilder.append(
"5. ОБЯЗАТЕЛЬНЫЕ ЦИФРЫ: в тексте обязательно упомяни, что объем рынка = 15 млрд тенге, а темпы роста = 12%.\n");
promptBuilder.append(
" Эти цифры должны быть явно написаны в тексте (как минимум 1 раз) и использованы в выводах.\n\n");
promptBuilder.append("СТРУКТУРА РАЗДЕЛА:\n");
promptBuilder.append("I. Краткое резюме\n\n");
promptBuilder.append("Опиши:\n");
@@ -1769,7 +1798,9 @@ public class MarketingAnalysisService {
promptBuilder.append("- Начинай сразу с заголовка \"III. Анализ рынка и сезонности\"\n");
promptBuilder.append("- Не добавляй вступления или заключения\n");
promptBuilder.append("- Используй данные из полей seasonality, market, regions\n");
promptBuilder.append("- Пиши максимально подробно, анализируй тренды\n\n");
promptBuilder.append("- Пиши максимально подробно, анализируй тренды\n");
promptBuilder.append(
"- Каждое ключевое утверждение подтверждай конкретной цифрой из JSON (или явно помечай \"нет данных в JSON\")\n\n");
promptBuilder.append("JSON ДАННЫЕ:\n");
promptBuilder.append(jsonString);
@@ -1803,10 +1834,13 @@ public class MarketingAnalysisService {
promptBuilder.append("1. Объем: минимум 500-700 слов\n");
promptBuilder.append("2. Стиль: деловой, убедительный для владельца бизнеса\n");
promptBuilder.append("3. Глубина: аналитические инсайты с практическими выводами\n");
promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n\n");
promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n");
promptBuilder.append(
"5. ЦИФРЫ ОБЯЗАТЕЛЬНЫ: в каждом подпункте делай выводы на основе процентов/чисел из audienceAnalysis.ageGroups и audienceAnalysis.genderDistribution.\n\n");
promptBuilder.append("СТРУКТУРА РАЗДЕЛА:\n");
promptBuilder.append("IV. Анализ целевой аудитории\n\n");
promptBuilder.append("Детально проанализируй КАЖДЫЙ сегмент из audienceSegments:\n");
promptBuilder.append(
"Детально проанализируй аудиторию на основе audienceAnalysis (segments, ageGroups, genderDistribution):\n");
promptBuilder.append("- Демографические характеристики (возраст, пол, доход)\n");
promptBuilder.append("- Психографический портрет (потребности, мотивация, ценности)\n");
promptBuilder.append(
@@ -1814,18 +1848,30 @@ public class MarketingAnalysisService {
promptBuilder.append("- Ключевые потребности и боли сегмента\n");
promptBuilder.append("- Как продукт решает проблемы каждого сегмента\n");
promptBuilder.append("- Рекомендации по коммуникации с каждым сегментом\n\n");
promptBuilder.append("КРИТИЧНО - ВСТАВКА ГРАФИКА:\n");
promptBuilder.append("КРИТИЧНО - ВСТАВКА ГРАФИКОВ (2 шт):\n");
promptBuilder.append(
"1. В логичном месте раздела (после описания сегментов) вставь плейсхолдер [[CHART_AUDIENCE]] на отдельной строке\n");
"1. После разбора возрастных групп вставь плейсхолдер [[CHART_AGE_DISTRIBUTION]] на отдельной строке\n");
promptBuilder.append(
"2. ПЕРЕД графиком напиши вводный абзац (2-3 предложения), начинающийся с: \"Анализ данных на графике ниже показывает...\"\n");
" - ПЕРЕД графиком: вводный абзац (2-3 предложения), начинающийся с: \"Анализ данных на графике ниже показывает...\"\n");
promptBuilder.append(
"3. ПОСЛЕ графика напиши интерпретацию данных (2-3 предложения), начинающуюся с: \"Из этих показателей следует, что...\"\n\n");
" - ПОСЛЕ графика: интерпретация (2-3 предложения), начинающаяся с: \"Из этих показателей следует, что...\"\n");
promptBuilder.append(
"2. После разбора гендерного распределения вставь плейсхолдер [[CHART_GENDER_DISTRIBUTION]] на отдельной строке\n");
promptBuilder.append(
" - ПЕРЕД графиком: вводный абзац (2-3 предложения), начинающийся с: \"Анализ данных на графике ниже показывает...\"\n");
promptBuilder.append(
" - ПОСЛЕ графика: интерпретация (2-3 предложения), начинающаяся с: \"Из этих показателей следует, что...\"\n\n");
promptBuilder.append(
"ОБЯЗАТЕЛЬНО ПРО ГЕНДЕР:\n- Явно укажи долю женщин по данным genderDistribution (в типовом кейсе около 60%) и объясни, почему аудитория смещена в сторону женщин.\n");
promptBuilder.append(
"- Дай практические рекомендации, как использовать это: месседжи, офферы, визуалы, каналы, время коммуникации.\n\n");
promptBuilder.append("ВАЖНО:\n");
promptBuilder.append("- Начинай сразу с заголовка \"IV. Анализ целевой аудитории\"\n");
promptBuilder.append("- Не добавляй вступления или заключения\n");
promptBuilder.append("- Используй данные из поля audienceSegments\n");
promptBuilder.append("- Для каждого сегмента создай детальный портрет клиента\n\n");
promptBuilder
.append("- Используй данные из поля audienceAnalysis (и только из него), не выдумывай проценты\n");
promptBuilder.append(
"- Для каждого сегмента создай детальный портрет клиента и привяжи его к возрастным и гендерным долям\n\n");
promptBuilder.append("JSON ДАННЫЕ:\n");
promptBuilder.append(jsonString);
@@ -1859,30 +1905,48 @@ public class MarketingAnalysisService {
promptBuilder.append("1. Объем: минимум 500-700 слов\n");
promptBuilder.append("2. Стиль: деловой, убедительный для владельца бизнеса\n");
promptBuilder.append("3. Глубина: аналитические инсайты с практическими выводами\n");
promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n\n");
promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n");
promptBuilder.append(
"5. Это должен быть самый мощный раздел отчёта: каждое утверждение подтверждай цифрами из competitorAnalysis.marketShareChart / competitorAnalysis.comparisonTable.\n\n");
promptBuilder.append("СТРУКТУРА РАЗДЕЛА:\n");
promptBuilder.append("V. Анализ конкурентов\n\n");
promptBuilder.append("Детально проанализируй:\n");
promptBuilder.append("- Позиции каждого конкурента на рынке (используй данные из competitors)\n");
promptBuilder.append("- Сильные и слабые стороны конкурентов (из comparisonTable и swotCompetitors)\n");
promptBuilder.append("- Позиции каждого конкурента на рынке (используй данные из competitorAnalysis)\n");
promptBuilder.append("- Анализ долей рынка (используй competitorAnalysis.marketShareChart)\n");
promptBuilder.append(
"- Сильные и слабые стороны конкурентов (из competitorAnalysis.comparisonTable и swotCompetitors)\n");
promptBuilder.append("- Стратегии конкурентов (ценовая политика, каналы продвижения)\n");
promptBuilder.append(
"- Контент-воронки конкурентов: какие типы контента они используют на каждом этапе воронки, какие каналы задействуют, какие месседжи доносят\n");
promptBuilder.append("- Глубокое сравнение нашего бренда с конкурентами (используй comparisonTable)\n");
promptBuilder.append(
"- Глубокое сравнение нашего бренда с конкурентами (используй competitorAnalysis.comparisonTable)\n");
promptBuilder.append("- Рекомендации по дифференциации и конкурентным преимуществам\n\n");
promptBuilder.append("КРИТИЧНО - ВСТАВКА ГРАФИКА:\n");
promptBuilder.append("КРИТИЧНО - ВСТАВКА ГРАФИКА И ТАБЛИЦЫ:\n");
promptBuilder.append(
"1. В логичном месте раздела (после анализа конкурентов) вставь плейсхолдер [[CHART_COMPETITORS]] на отдельной строке\n");
"1. После анализа долей рынка вставь плейсхолдер [[CHART_MARKET_SHARE]] на отдельной строке\n");
promptBuilder.append(
"2. ПЕРЕД графиком напиши вводный абзац (2-3 предложения), начинающийся с: \"Анализ данных на графике ниже показывает...\"\n");
" - ПЕРЕД графиком: вводный абзац (2-3 предложения), начинающийся с: \"Анализ данных на графике ниже показывает...\"\n");
promptBuilder.append(
"3. ПОСЛЕ графика напиши интерпретацию данных (2-3 предложения), начинающуюся с: \"Из этих показателей следует, что...\"\n\n");
" - ПОСЛЕ графика: интерпретация (2-3 предложения), начинающаяся с: \"Из этих показателей следует, что...\"\n");
promptBuilder.append(
"2. Затем вставь плейсхолдер [[TABLE_COMPARISON]] на отдельной строке (это сравнительная таблица)\n");
promptBuilder.append(
" - ПЕРЕД таблицей: короткий ввод (1-2 предложения), что именно сравниваем\n");
promptBuilder.append(
" - ПОСЛЕ таблицы: интерпретация (3-5 предложений) с выводом по позиционированию\n\n");
promptBuilder.append(
"ОБЯЗАТЕЛЬНОЕ СРАВНЕНИЕ С 'КОНКУРЕНТОМ A' И 'КОНКУРЕНТОМ B':\n");
promptBuilder.append(
"- Возьми двух главных конкурентов из competitorAnalysis (по доле рынка или по описанию) и назови их в тексте как \"Конкурент A\" и \"Конкурент B\".\n");
promptBuilder.append(
"- Пройди ПО КАЖДОЙ строке competitorAnalysis.comparisonTable: Мы vs Конкурент A vs Конкурент B (с цифрами/формулировками из JSON) + практический вывод.\n\n");
promptBuilder.append("ВАЖНО:\n");
promptBuilder.append("- Начинай сразу с заголовка \"V. Анализ конкурентов\"\n");
promptBuilder.append("- Не добавляй вступления или заключения\n");
promptBuilder.append(
"- Используй данные из полей competitors, comparisonTable, swotCompetitors, marketShareChart\n");
promptBuilder.append("- Делай глубокие сравнения нашего бренда с конкурентами\n\n");
"- Используй данные из competitorAnalysis.marketShareChart и competitorAnalysis.comparisonTable\n");
promptBuilder.append(
"- Не выдумывай цифры: если по строке сравнения нет данных — явно пометь это\n\n");
promptBuilder.append("JSON ДАННЫЕ:\n");
promptBuilder.append(jsonString);
@@ -2195,12 +2259,21 @@ public class MarketingAnalysisService {
promptBuilder.append("- Ожидаемые результаты и метрики успеха\n");
promptBuilder.append("- Бюджетные рекомендации и распределение ресурсов\n");
promptBuilder.append("- Критические точки контроля и корректировки стратегии\n\n");
promptBuilder.append("КРИТИЧНО - ПРОГНОЗ И РАСЧЕТЫ НА ОСНОВЕ ВОРОНКИ:\n");
promptBuilder.append(
"1. Возьми воронку из strategyAnalysis.funnel (или из funnel, если strategyAnalysis отсутствует) и выполни расчеты.\n");
promptBuilder.append(
"2. Дай минимум 3 прогнозных показателя с формулами и подстановкой чисел из JSON (например: конверсия между этапами, ожидаемое число покупок, требуемый объем верхнего этапа для достижения цели).\n");
promptBuilder.append(
"3. Сделай 3 сценария: консервативный / базовый / оптимистичный (допуски только как проценты к значениям из JSON; не придумывай абсолютные значения).\n");
promptBuilder.append(
"4. Если данных для метрики нет в JSON — прямо напиши \"нет данных в JSON\" и не рассчитывай её.\n\n");
promptBuilder.append("ВАЖНО:\n");
promptBuilder
.append("- Начинай сразу с заголовка \"XI. Итоговая стратегия: что делать в первую очередь\"\n");
promptBuilder.append("- Не добавляй вступления или заключения\n");
promptBuilder.append(
"- Используй данные из всех предыдущих разделов (swot, channels, funnel, positioning, valueProposition, contentRecommendations)\n");
"- Используй данные из strategyAnalysis (и/или swot/channels/funnel при наличии), positioning, valueProposition, contentRecommendations\n");
promptBuilder.append("- Давай конкретный, практический план действий с приоритетами\n");
promptBuilder.append("- Учитывай цель бизнеса на 6-12 месяцев (goal)\n\n");
promptBuilder.append("JSON ДАННЫЕ:\n");
@@ -3466,6 +3539,10 @@ public class MarketingAnalysisService {
.replace("[[CHART_SEASONALITY]]", "")
.replace("[[CHART_AUDIENCE]]", "")
.replace("[[CHART_COMPETITORS]]", "")
.replace("[[CHART_MARKET_SHARE]]", "")
.replace("[[TABLE_COMPARISON]]", "")
.replace("[[CHART_AGE_DISTRIBUTION]]", "")
.replace("[[CHART_GENDER_DISTRIBUTION]]", "")
.replace("[[CHART_CHANNELS]]", "")
.replace("[[CHART_FUNNEL]]", "")
.replace("[[CHART_SWOT]]", "");
@@ -3754,6 +3831,10 @@ public class MarketingAnalysisService {
.replace("[[CHART_SEASONALITY]]", "")
.replace("[[CHART_AUDIENCE]]", "")
.replace("[[CHART_COMPETITORS]]", "")
.replace("[[CHART_MARKET_SHARE]]", "")
.replace("[[TABLE_COMPARISON]]", "")
.replace("[[CHART_AGE_DISTRIBUTION]]", "")
.replace("[[CHART_GENDER_DISTRIBUTION]]", "")
.replace("[[CHART_CHANNELS]]", "")
.replace("[[CHART_FUNNEL]]", "")
.replace("[[CHART_SWOT]]", "");
@@ -3770,6 +3851,10 @@ public class MarketingAnalysisService {
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_MARKET_SHARE]]", new ChartMapping("marketShareChart", "marketShareChart"));
placeholderMap.put("[[TABLE_COMPARISON]]", new ChartMapping("comparisonTable", "comparisonTable"));
placeholderMap.put("[[CHART_AGE_DISTRIBUTION]]", new ChartMapping("audienceAge", "audienceAge"));
placeholderMap.put("[[CHART_GENDER_DISTRIBUTION]]", new ChartMapping("audienceGender", "audienceGender"));
placeholderMap.put("[[CHART_CHANNELS]]", new ChartMapping("channelsPotential", "channelsPotential"));
placeholderMap.put("[[CHART_FUNNEL]]", new ChartMapping("conversionFunnel", "funnel"));
placeholderMap.put("[[CHART_SWOT]]", new ChartMapping("swot", "swot"));
@@ -3828,7 +3913,8 @@ public class MarketingAnalysisService {
// Финальная очистка: удаляем все оставшиеся не замененные плейсхолдеры
// (если AI вернул их, но данных в chartsData нет)
java.util.regex.Pattern remainingPlaceholderPattern = java.util.regex.Pattern.compile("\\[\\[CHART_\\w+\\]\\]");
java.util.regex.Pattern remainingPlaceholderPattern = java.util.regex.Pattern
.compile("\\[\\[(CHART|TABLE)_\\w+\\]\\]");
java.util.regex.Matcher matcher = remainingPlaceholderPattern.matcher(result);
Set<String> remainingPlaceholders = new HashSet<>();
while (matcher.find()) {
@@ -3886,31 +3972,61 @@ public class MarketingAnalysisService {
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]]");
// Маппинг разделов на плейсхолдеры (может быть несколько плейсхолдеров в одном
// разделе)
Map<String, List<String>> sectionToPlaceholders = new HashMap<>();
sectionToPlaceholders.put("III\\.\\s*Анализ рынка и сезонности", List.of("[[CHART_SEASONALITY]]"));
sectionToPlaceholders.put("IV\\.\\s*Анализ целевой аудитории",
List.of("[[CHART_AGE_DISTRIBUTION]]", "[[CHART_GENDER_DISTRIBUTION]]"));
sectionToPlaceholders.put("V\\.\\s*Анализ конкурентов",
List.of("[[CHART_MARKET_SHARE]]", "[[TABLE_COMPARISON]]"));
sectionToPlaceholders.put("VII\\.\\s*Каналы продвижения", List.of("[[CHART_CHANNELS]]"));
sectionToPlaceholders.put("VIII\\.\\s*Воронка спроса", List.of("[[CHART_FUNNEL]]"));
sectionToPlaceholders.put("VI\\.\\s*SWOT-анализ", List.of("[[CHART_SWOT]]"));
for (Map.Entry<String, String> sectionEntry : sectionToPlaceholder.entrySet()) {
for (Map.Entry<String, List<String>> sectionEntry : sectionToPlaceholders.entrySet()) {
String sectionPattern = sectionEntry.getKey();
String placeholder = sectionEntry.getValue();
List<String> placeholders = sectionEntry.getValue();
// Если плейсхолдер уже был обработан, пропускаем
if (processedPlaceholders.contains(placeholder)) {
if (placeholders == null || placeholders.isEmpty()) {
continue;
}
ChartMapping mapping = placeholderMap.get(placeholder);
if (mapping == null) {
continue;
StringBuilder blocksToInsert = new StringBuilder();
boolean hasAnyBlock = false;
for (String placeholder : placeholders) {
if (placeholder == null || placeholder.isBlank()) {
continue;
}
// Если плейсхолдер уже был обработан (присутствовал в тексте), пропускаем его
if (processedPlaceholders.contains(placeholder)) {
continue;
}
ChartMapping mapping = placeholderMap.get(placeholder);
if (mapping == null) {
continue;
}
Object chartData = chartsData.get(mapping.dataKey);
if (isEmptyChartData(chartData)) {
continue;
}
try {
String jsonStr = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(chartData);
String jsonBlock = "\n\n```json:" + mapping.chartKey + "\n" + jsonStr + "\n```\n";
blocksToInsert.append(jsonBlock);
hasAnyBlock = true;
// Помечаем как обработанный, чтобы не вставить второй раз
processedPlaceholders.add(placeholder);
} catch (JsonProcessingException e) {
logger.warn("Failed to serialize chart data for fallback {}: {}", mapping.dataKey, e.getMessage());
}
}
Object chartData = chartsData.get(mapping.dataKey);
if (isEmptyChartData(chartData)) {
if (!hasAnyBlock) {
continue;
}
@@ -3932,48 +4048,41 @@ public class MarketingAnalysisService {
}
if (found) {
try {
String jsonStr = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(chartData);
String jsonBlock = "\n\n```json:" + mapping.chartKey + "\n" + jsonStr + "\n```\n";
// Вставляем после найденного раздела, перед следующим разделом или концом
// текста
int insertPos = matcher.end();
// Вставляем после найденного заголовка раздела, перед следующим разделом или
// концом текста
int insertPos = matcher.end();
// Ищем конец текущего раздела (начало следующего раздела или конец текста)
String remainingText = result.substring(insertPos);
// Ищем конец текущего раздела (начало следующего раздела или конец текста)
String remainingText = result.substring(insertPos);
// Паттерн для поиска следующего раздела: может быть markdown заголовок или
// римская цифра
java.util.regex.Pattern nextSectionPattern = java.util.regex.Pattern
.compile("^(##+\\s*[IVX]+\\.|\\s*[IVX]+\\.\\s+[А-Я])", java.util.regex.Pattern.MULTILINE);
java.util.regex.Matcher nextSectionMatcher = nextSectionPattern.matcher(remainingText);
// Паттерн для поиска следующего раздела: может быть markdown заголовок или
// римская цифра
java.util.regex.Pattern nextSectionPattern = java.util.regex.Pattern
.compile("^(##+\\s*[IVX]+\\.|\\s*[IVX]+\\.\\s+[А-Я])", java.util.regex.Pattern.MULTILINE);
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);
String blocks = blocksToInsert.toString();
if (nextSectionMatcher.find()) {
// Вставляем перед следующим разделом
int nextSectionPos = insertPos + nextSectionMatcher.start();
result = result.substring(0, nextSectionPos) + blocks + result.substring(nextSectionPos);
} else {
// Ищем следующий раздел по римской цифре
java.util.regex.Pattern romanNumeralPattern = java.util.regex.Pattern
.compile("\\n\\s*[IVX]+\\.\\s+[А-Я]", java.util.regex.Pattern.MULTILINE);
java.util.regex.Matcher romanMatcher = romanNumeralPattern.matcher(remainingText);
if (romanMatcher.find()) {
int nextSectionPos = insertPos + romanMatcher.start();
result = result.substring(0, nextSectionPos) + blocks + result.substring(nextSectionPos);
} else {
// Ищем конец текущего раздела по содержимому (до следующего раздела с римской
// цифрой)
// Ищем следующий раздел по паттерну: начало строки, римская цифра, точка,
// пробел, заглавная буква
java.util.regex.Pattern romanNumeralPattern = java.util.regex.Pattern
.compile("\\n\\s*[IVX]+\\.\\s+[А-Я]", java.util.regex.Pattern.MULTILINE);
java.util.regex.Matcher romanMatcher = romanNumeralPattern.matcher(remainingText);
if (romanMatcher.find()) {
int nextSectionPos = insertPos + romanMatcher.start();
result = result.substring(0, nextSectionPos) + jsonBlock + result.substring(nextSectionPos);
} else {
// Вставляем в конец текста
result = result + jsonBlock;
}
// Вставляем в конец текста
result = result + blocks;
}
logger.debug("addMissingChartsToSections: Added chart {} to section matching pattern: {}",
mapping.dataKey, sectionPattern);
} catch (JsonProcessingException e) {
logger.warn("Failed to serialize chart data for fallback {}: {}", mapping.dataKey, e.getMessage());
}
logger.debug("addMissingChartsToSections: Added fallback blocks to section matching pattern: {}",
sectionPattern);
} else {
logger.debug("addMissingChartsToSections: Section not found for pattern: {}", sectionPattern);
}