This commit is contained in:
root
2025-12-18 23:55:49 +05:00
parent dbfd441c28
commit b140fe35f5
2 changed files with 114 additions and 16 deletions
@@ -1441,16 +1441,32 @@ public class MarketingAnalysisService {
"делай глубокие сравнения нашего бренда с конкурентами (используй данные из 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(
"4. КРИТИЧНО: Каждый раздел (I-XI) должен содержать минимум 3-4 детальных абзаца с глубокой аналитикой\n");
promptBuilder.append("5. Используй данные ТОЛЬКО из переданного JSON, не выдумывай цифры\n");
promptBuilder.append("6. Пиши конкретно и практично, избегай общих фраз\n");
"6. Обязательно используй данные из полей product, businessNiche, goal, regions для контекста анализа\n\n");
promptBuilder.append("АНАЛИТИЧЕСКАЯ ГЛУБИНА:\n");
promptBuilder.append("1. Используй \"Метод Case Study\" и \"Бенчмаркинг\" для глубокого анализа\n");
promptBuilder
.append("2. НЕ просто перечисляй данные из JSON, а ИНТЕРПРЕТИРУЙ их с практическими выводами\n");
promptBuilder.append(
"7. Обязательно используй данные из полей product, businessNiche, goal, regions для контекста анализа\n\n");
"3. Приводи конкретные примеры: если сезонность падает в июле, объясни, как именно это влияет на кэшфлоу бизнеса и предложи конкретные акции для перекрытия кассового разрыва\n");
promptBuilder.append(
"4. Каждое утверждение должно быть подкреплено конкретными данными из JSON с объяснением их бизнес-значения\n");
promptBuilder.append(
"5. Делай глубокие сравнения нашего бренда с конкурентами (используй данные из competitorAnalysis и comparisonTable)\n\n");
promptBuilder.append("ОБЯЗАТЕЛЬНАЯ СТРУКТУРА ОТЧЕТА:\n");
promptBuilder.append("I. Краткое резюме (общий обзор бизнеса и ключевые выводы)\n");
@@ -1471,19 +1487,37 @@ public class MarketingAnalysisService {
promptBuilder.append("X. Рекомендации по контенту (типы контента для разных целей)\n");
promptBuilder.append("XI. Итоговая стратегия: что делать в первую очередь (конкретный план действий)\n\n");
promptBuilder.append("КРИТИЧНО - ПЛЕЙСХОЛДЕРЫ ГРАФИКОВ:\n");
promptBuilder.append("КРИТИЧНО - ПРИНУДИТЕЛЬНАЯ ВСТАВКА ГРАФИКОВ:\n");
promptBuilder.append(
"Обязательно расставь ВСЕ плейсхолдеры графиков: [[CHART_SEASONALITY]], [[CHART_AUDIENCE]], ");
promptBuilder.append("[[CHART_COMPETITORS]], [[CHART_SWOT]], [[CHART_CHANNELS]], [[CHART_FUNNEL]]. ");
promptBuilder.append("Каждый плейсхолдер должен быть на отдельной строке в соответствующем разделе, ");
promptBuilder.append("как указано в структуре выше. НЕ пропускай ни одного плейсхолдера!\n\n");
"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\n");
promptBuilder.append("- Пиши так, чтобы отчет был полезен для принятия бизнес-решений\n");
promptBuilder.append(
"- Цель: получить полноценное маркетинговое исследование на 15+ страниц с глубокой аналитикой и визуализациями\n\n");
promptBuilder.append("JSON ДАННЫЕ:\n");
promptBuilder.append(jsonString);
@@ -2860,6 +2894,36 @@ public class MarketingAnalysisService {
logger.debug("replaceChartPlaceholders: Adding missing charts to sections");
result = addMissingChartsToSections(result, chartsData, placeholderMap, processedPlaceholders);
// Финальная очистка: удаляем все оставшиеся не замененные плейсхолдеры
// (если AI вернул их, но данных в chartsData нет)
java.util.regex.Pattern remainingPlaceholderPattern = java.util.regex.Pattern.compile("\\[\\[CHART_\\w+\\]\\]");
java.util.regex.Matcher matcher = remainingPlaceholderPattern.matcher(result);
Set<String> remainingPlaceholders = new HashSet<>();
while (matcher.find()) {
String placeholder = matcher.group();
remainingPlaceholders.add(placeholder);
}
for (String placeholder : remainingPlaceholders) {
// Проверяем, есть ли данные для этого плейсхолдера
boolean hasData = false;
ChartMapping mapping = placeholderMap.get(placeholder);
if (mapping != null) {
Object chartData = chartsData.get(mapping.dataKey);
if (!isEmptyChartData(chartData)) {
hasData = true;
}
}
if (!hasData) {
logger.warn("replaceChartPlaceholders: Removing unprocessed placeholder without data: {}", placeholder);
// Удаляем плейсхолдер с окружающими пустыми строками
String placeholderWithNewlines = "(\\r?\\n)?\\s*" + java.util.regex.Pattern.quote(placeholder)
+ "\\s*(\\r?\\n)?";
result = result.replaceAll(placeholderWithNewlines, "\n");
}
}
// Финальная очистка: удаляем множественные пустые строки (более 2 подряд)
result = result.replaceAll("(\\r?\\n){3,}", "\n\n");
@@ -2918,21 +2982,38 @@ public class MarketingAnalysisService {
continue;
}
// Ищем раздел в тексте
// Ищем раздел в тексте: сначала по markdown заголовкам, затем по римским цифрам
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);
boolean found = matcher.find();
if (matcher.find()) {
// Если не нашли по markdown, ищем по римским цифрам (I., II., III. и т.д.)
if (!found) {
// Паттерн для поиска раздела по римским цифрам: I. Название, II. Название и
// т.д.
pattern = java.util.regex.Pattern
.compile("(^|\\n)\\s*" + sectionPattern + "[^\\n]*(\\n|$)",
java.util.regex.Pattern.CASE_INSENSITIVE | java.util.regex.Pattern.MULTILINE);
matcher = pattern.matcher(result);
found = matcher.find();
}
if (found) {
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]+\\.");
// Паттерн для поиска следующего раздела: может быть 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()) {
@@ -2940,12 +3021,29 @@ public class MarketingAnalysisService {
int nextSectionPos = insertPos + nextSectionMatcher.start();
result = result.substring(0, nextSectionPos) + jsonBlock + result.substring(nextSectionPos);
} else {
// Вставляем в конец текста
result = result + jsonBlock;
// Ищем конец текущего раздела по содержимому (до следующего раздела с римской
// цифрой)
// Ищем следующий раздел по паттерну: начало строки, римская цифра, точка,
// пробел, заглавная буква
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;
}
}
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());
}
} else {
logger.debug("addMissingChartsToSections: Section not found for pattern: {}", sectionPattern);
}
}
@@ -264,7 +264,7 @@ public class OpenAIAnalyticsService {
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", modelName);
requestBody.put("messages", List.of(message));
requestBody.put("max_tokens", 2000);
requestBody.put("max_tokens", 12000); // Увеличено для поддержки отчетов объемом 2500-3000 слов
requestBody.put("temperature", 0.3);
try {