.
This commit is contained in:
@@ -1429,17 +1429,28 @@ public class MarketingAnalysisService {
|
||||
logger.debug("generatePolishedTextReport: JSON string length: {}", jsonString.length());
|
||||
|
||||
StringBuilder promptBuilder = new StringBuilder();
|
||||
promptBuilder.append("Ты — опытный маркетинговый аналитик и бизнес-консультант.\n\n");
|
||||
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. Используй деловой стиль письма, убедительный для владельца бизнеса\n");
|
||||
promptBuilder.append("2. Добавляй аналитические инсайты и практические выводы\n");
|
||||
promptBuilder.append("3. Структурируй текст по разделам с римскими цифрами (I, II, III и т.д.)\n");
|
||||
promptBuilder.append("4. Каждый раздел должен быть содержательным (минимум 2-3 абзаца)\n");
|
||||
promptBuilder.append(
|
||||
"4. КРИТИЧНО: Каждый раздел (I-XI) должен содержать минимум 3-4 детальных абзаца с глубокой аналитикой\n");
|
||||
promptBuilder.append("5. Используй данные ТОЛЬКО из переданного JSON, не выдумывай цифры\n");
|
||||
promptBuilder.append("6. Пиши конкретно и практично, избегай общих фраз\n\n");
|
||||
promptBuilder.append("6. Пиши конкретно и практично, избегай общих фраз\n");
|
||||
promptBuilder.append(
|
||||
"7. Обязательно используй данные из полей product, businessNiche, goal, regions для контекста анализа\n\n");
|
||||
|
||||
promptBuilder.append("ОБЯЗАТЕЛЬНАЯ СТРУКТУРА ОТЧЕТА:\n");
|
||||
promptBuilder.append("I. Краткое резюме (общий обзор бизнеса и ключевые выводы)\n");
|
||||
@@ -1460,6 +1471,13 @@ public class MarketingAnalysisService {
|
||||
promptBuilder.append("X. Рекомендации по контенту (типы контента для разных целей)\n");
|
||||
promptBuilder.append("XI. Итоговая стратегия: что делать в первую очередь (конкретный план действий)\n\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");
|
||||
|
||||
promptBuilder.append("ВАЖНО:\n");
|
||||
promptBuilder.append("- Плейсхолдеры [[CHART_...]] вставляй ТОЧНО как указано выше, на отдельной строке\n");
|
||||
promptBuilder.append("- Начинай сразу с 'I. Краткое резюме', без вступлений\n");
|
||||
@@ -2432,22 +2450,9 @@ public class MarketingAnalysisService {
|
||||
report.put("analysisTypes", analysisTypeNames);
|
||||
report.put("detailLevel", detailLevelName);
|
||||
report.put("fullAnalysis", "Не удалось сгенерировать анализ. Пожалуйста, попробуйте позже.");
|
||||
report.put("summary", "Не удалось сгенерировать анализ. Пожалуйста, попробуйте позже.");
|
||||
report.put("summary", "Маркетинговый анализ для " + request.getProduct());
|
||||
report.put("chartsData", new HashMap<>());
|
||||
|
||||
// Добавляем дефолтные значения для обратной совместимости
|
||||
Map<String, Object> defaultTargetAudience = new HashMap<>();
|
||||
defaultTargetAudience.put("description", "Не удалось сгенерировать описание целевой аудитории.");
|
||||
defaultTargetAudience.put("channels", Arrays.asList("Instagram", "LinkedIn", "Telegram"));
|
||||
report.put("targetAudience", defaultTargetAudience);
|
||||
report.put("recommendations", Arrays.asList("Не удалось сгенерировать рекомендации."));
|
||||
|
||||
Map<String, Object> defaultStrategy = new HashMap<>();
|
||||
defaultStrategy.put("duration", "1 месяц");
|
||||
defaultStrategy.put("channels", Arrays.asList("Instagram", "Telegram", "LinkedIn"));
|
||||
defaultStrategy.put("contentTypes", Arrays.asList("посты", "сторис", "видео"));
|
||||
report.put("strategy", defaultStrategy);
|
||||
|
||||
logger.warn("generateMarketingReport: Returning report with error message");
|
||||
} else {
|
||||
logger.info("generateMarketingReport: JSON analysis generated successfully. Keys: {}",
|
||||
@@ -2507,27 +2512,12 @@ public class MarketingAnalysisService {
|
||||
report.put("fullAnalysis",
|
||||
processedTextReport != null ? processedTextReport : "Анализ не удалось сгенерировать.");
|
||||
|
||||
// Извлекаем только первую секцию (I. Краткое резюме) для поля summary
|
||||
// чтобы избежать дублирования полного текста отчета
|
||||
String shortSummary = extractSummarySection(processedTextReport);
|
||||
report.put("summary", shortSummary != null ? shortSummary : "Анализ не удалось сгенерировать.");
|
||||
|
||||
// Извлекаем данные из analysisJson вместо повторных вызовов AI
|
||||
logger.info("generateMarketingReport: Extracting targetAudience from analysisJson");
|
||||
Map<String, Object> targetAudience = extractTargetAudienceFromJson(analysisJson);
|
||||
report.put("targetAudience", targetAudience);
|
||||
logger.info("generateMarketingReport: TargetAudience extracted. Channels count: {}",
|
||||
((List<?>) targetAudience.get("channels")).size());
|
||||
|
||||
logger.info("generateMarketingReport: Extracting recommendations from analysisJson");
|
||||
List<String> recommendations = extractRecommendationsFromJson(analysisJson);
|
||||
report.put("recommendations", recommendations);
|
||||
logger.info("generateMarketingReport: Recommendations extracted. Count: {}", recommendations.size());
|
||||
|
||||
logger.info("generateMarketingReport: Extracting strategy from analysisJson");
|
||||
Map<String, Object> strategy = extractStrategyFromJson(analysisJson);
|
||||
report.put("strategy", strategy);
|
||||
logger.info("generateMarketingReport: Strategy extracted. Channels: {}", strategy.get("channels"));
|
||||
// Summary содержит только короткую вводную фразу
|
||||
String summary = "Маркетинговый анализ для " + request.getProduct();
|
||||
if (request.getBusinessNiche() != null && !request.getBusinessNiche().trim().isEmpty()) {
|
||||
summary += " (" + request.getBusinessNiche() + ")";
|
||||
}
|
||||
report.put("summary", summary);
|
||||
}
|
||||
|
||||
logger.info("generateMarketingReport: Report generation completed. Report keys: {}", report.keySet());
|
||||
@@ -2682,322 +2672,6 @@ public class MarketingAnalysisService {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает описание целевой аудитории и каналы из JSON-анализа.
|
||||
* Заменяет повторный вызов AI для генерации описания аудитории.
|
||||
*
|
||||
* @param analysisJson JSON с данными маркетингового анализа
|
||||
* @return Map с description и channels
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> extractTargetAudienceFromJson(Map<String, Object> analysisJson) {
|
||||
Map<String, Object> targetAudience = new HashMap<>();
|
||||
StringBuilder descriptionBuilder = new StringBuilder();
|
||||
List<String> channels = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// Извлекаем данные из audienceAnalysis
|
||||
Map<String, Object> audienceAnalysis = (Map<String, Object>) analysisJson.get("audienceAnalysis");
|
||||
if (audienceAnalysis != null) {
|
||||
// Получаем ключевые выводы
|
||||
List<String> keyTakeaways = (List<String>) audienceAnalysis.get("keyTakeaways");
|
||||
if (keyTakeaways != null && !keyTakeaways.isEmpty()) {
|
||||
descriptionBuilder.append("Ключевые характеристики целевой аудитории:\n");
|
||||
for (String takeaway : keyTakeaways) {
|
||||
descriptionBuilder.append("• ").append(takeaway).append("\n");
|
||||
}
|
||||
descriptionBuilder.append("\n");
|
||||
}
|
||||
|
||||
// Получаем информацию о сегментах
|
||||
List<Map<String, Object>> segments = (List<Map<String, Object>>) audienceAnalysis.get("segments");
|
||||
if (segments != null && !segments.isEmpty()) {
|
||||
descriptionBuilder.append("Основные сегменты аудитории:\n");
|
||||
for (Map<String, Object> segment : segments) {
|
||||
Object name = segment.get("name");
|
||||
Object sharePercent = segment.get("sharePercent");
|
||||
Object ageRange = segment.get("ageRange");
|
||||
Object motivation = segment.get("motivation");
|
||||
|
||||
if (name != null) {
|
||||
descriptionBuilder.append("\n• ").append(name);
|
||||
if (sharePercent != null) {
|
||||
descriptionBuilder.append(" (").append(sharePercent).append("%)");
|
||||
}
|
||||
descriptionBuilder.append("\n");
|
||||
}
|
||||
if (ageRange != null) {
|
||||
descriptionBuilder.append(" Возраст: ").append(ageRange).append("\n");
|
||||
}
|
||||
if (motivation != null) {
|
||||
descriptionBuilder.append(" Мотивация: ").append(motivation).append("\n");
|
||||
}
|
||||
|
||||
// Собираем каналы из сегментов
|
||||
List<String> primaryChannels = (List<String>) segment.get("primaryChannels");
|
||||
if (primaryChannels != null) {
|
||||
for (String channel : primaryChannels) {
|
||||
if (!channels.contains(channel)) {
|
||||
channels.add(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Комментарии по гендеру и возрасту
|
||||
Object genderComment = audienceAnalysis.get("genderComment");
|
||||
Object ageComment = audienceAnalysis.get("ageComment");
|
||||
if (genderComment != null || ageComment != null) {
|
||||
descriptionBuilder.append("\n");
|
||||
if (ageComment != null) {
|
||||
descriptionBuilder.append(ageComment).append("\n");
|
||||
}
|
||||
if (genderComment != null) {
|
||||
descriptionBuilder.append(genderComment).append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем каналы из channels.recommended
|
||||
Map<String, Object> channelsData = (Map<String, Object>) analysisJson.get("channels");
|
||||
if (channelsData != null) {
|
||||
List<Map<String, Object>> recommended = (List<Map<String, Object>>) channelsData.get("recommended");
|
||||
if (recommended != null) {
|
||||
for (Map<String, Object> channel : recommended) {
|
||||
Object channelName = channel.get("name");
|
||||
if (channelName != null && !channels.contains(channelName.toString())) {
|
||||
channels.add(channelName.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("extractTargetAudienceFromJson: Error extracting data: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Если описание пустое, добавляем дефолтное
|
||||
if (descriptionBuilder.length() == 0) {
|
||||
descriptionBuilder.append("Описание целевой аудитории доступно в полном отчете.");
|
||||
}
|
||||
|
||||
// Если каналы пустые, добавляем дефолтные
|
||||
if (channels.isEmpty()) {
|
||||
channels = Arrays.asList("Instagram", "LinkedIn", "Telegram");
|
||||
}
|
||||
|
||||
targetAudience.put("description", descriptionBuilder.toString());
|
||||
targetAudience.put("channels", channels);
|
||||
|
||||
return targetAudience;
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает рекомендации из JSON-анализа.
|
||||
* Заменяет повторный вызов AI для генерации рекомендаций.
|
||||
*
|
||||
* @param analysisJson JSON с данными маркетингового анализа
|
||||
* @return Список рекомендаций
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<String> extractRecommendationsFromJson(Map<String, Object> analysisJson) {
|
||||
List<String> recommendations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// Извлекаем из contentRecommendations
|
||||
Map<String, Object> contentRecommendations = (Map<String, Object>) analysisJson
|
||||
.get("contentRecommendations");
|
||||
if (contentRecommendations != null) {
|
||||
// Собираем рекомендации из всех типов контента
|
||||
List<String> salesContent = (List<String>) contentRecommendations.get("sales");
|
||||
List<String> expertContent = (List<String>) contentRecommendations.get("expertise");
|
||||
List<String> trustContent = (List<String>) contentRecommendations.get("trust");
|
||||
|
||||
if (salesContent != null && !salesContent.isEmpty()) {
|
||||
recommendations.add("Продающий контент: " + String.join(", ", salesContent));
|
||||
}
|
||||
if (expertContent != null && !expertContent.isEmpty()) {
|
||||
recommendations.add("Экспертный контент: " + String.join(", ", expertContent));
|
||||
}
|
||||
if (trustContent != null && !trustContent.isEmpty()) {
|
||||
recommendations.add("Контент для доверия: " + String.join(", ", trustContent));
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем из valueProposition
|
||||
Map<String, Object> valueProposition = (Map<String, Object>) analysisJson.get("valueProposition");
|
||||
if (valueProposition != null) {
|
||||
List<String> mainBenefits = (List<String>) valueProposition.get("mainBenefits");
|
||||
if (mainBenefits != null && !mainBenefits.isEmpty()) {
|
||||
recommendations.add("Основные преимущества для продвижения: " + String.join(", ", mainBenefits));
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем из SWOT - opportunities
|
||||
Map<String, Object> swot = (Map<String, Object>) analysisJson.get("swot");
|
||||
if (swot != null) {
|
||||
List<String> opportunities = (List<String>) swot.get("opportunities");
|
||||
if (opportunities != null && !opportunities.isEmpty()) {
|
||||
for (int i = 0; i < Math.min(3, opportunities.size()); i++) {
|
||||
recommendations.add("Использовать возможность: " + opportunities.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем из positioning
|
||||
Map<String, Object> positioning = (Map<String, Object>) analysisJson.get("positioning");
|
||||
if (positioning != null) {
|
||||
Object keyMessage = positioning.get("keyMessage");
|
||||
if (keyMessage != null) {
|
||||
recommendations.add("Ключевое сообщение: " + keyMessage);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("extractRecommendationsFromJson: Error extracting data: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Если рекомендации пустые, добавляем дефолтные
|
||||
if (recommendations.isEmpty()) {
|
||||
recommendations = Arrays.asList(
|
||||
"Рекомендации доступны в полном маркетинговом отчете",
|
||||
"Изучите раздел с SWOT-анализом для понимания возможностей",
|
||||
"Обратите внимание на рекомендуемые каналы продвижения");
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает стратегию из JSON-анализа.
|
||||
* Заменяет повторный вызов AI для генерации стратегии.
|
||||
*
|
||||
* @param analysisJson JSON с данными маркетингового анализа
|
||||
* @return Map со стратегией (duration, channels, contentTypes)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> extractStrategyFromJson(Map<String, Object> analysisJson) {
|
||||
Map<String, Object> strategy = new HashMap<>();
|
||||
List<String> channels = new ArrayList<>();
|
||||
List<String> contentTypes = new ArrayList<>();
|
||||
String duration = "1 месяц"; // default
|
||||
|
||||
try {
|
||||
// Извлекаем каналы из channels.recommended
|
||||
Map<String, Object> channelsData = (Map<String, Object>) analysisJson.get("channels");
|
||||
if (channelsData != null) {
|
||||
List<Map<String, Object>> recommended = (List<Map<String, Object>>) channelsData.get("recommended");
|
||||
if (recommended != null) {
|
||||
for (Map<String, Object> channel : recommended) {
|
||||
Object channelName = channel.get("name");
|
||||
if (channelName != null) {
|
||||
channels.add(channelName.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем типы контента из contentRecommendations
|
||||
Map<String, Object> contentRecommendations = (Map<String, Object>) analysisJson
|
||||
.get("contentRecommendations");
|
||||
if (contentRecommendations != null) {
|
||||
if (contentRecommendations.containsKey("sales")) {
|
||||
contentTypes.add("продающий контент");
|
||||
}
|
||||
if (contentRecommendations.containsKey("expertise")) {
|
||||
contentTypes.add("экспертный контент");
|
||||
}
|
||||
if (contentRecommendations.containsKey("trust")) {
|
||||
contentTypes.add("доверительный контент");
|
||||
}
|
||||
if (contentRecommendations.containsKey("entertainment")) {
|
||||
contentTypes.add("развлекательный контент");
|
||||
}
|
||||
}
|
||||
|
||||
// Извлекаем информацию о воронке для определения длительности
|
||||
Map<String, Object> funnel = (Map<String, Object>) analysisJson.get("funnel");
|
||||
if (funnel != null) {
|
||||
List<Map<String, Object>> stages = (List<Map<String, Object>>) funnel.get("stages");
|
||||
if (stages != null && stages.size() > 3) {
|
||||
// Если воронка сложная (более 3 этапов), рекомендуем более длительную кампанию
|
||||
duration = "2-3 месяца";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("extractStrategyFromJson: Error extracting data: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Дефолтные значения если данные не найдены
|
||||
if (channels.isEmpty()) {
|
||||
channels = Arrays.asList("Instagram", "Telegram", "LinkedIn");
|
||||
}
|
||||
if (contentTypes.isEmpty()) {
|
||||
contentTypes = Arrays.asList("посты", "сторис", "видео");
|
||||
}
|
||||
|
||||
strategy.put("duration", duration);
|
||||
strategy.put("channels", channels);
|
||||
strategy.put("contentTypes", contentTypes);
|
||||
|
||||
return strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлекает только первую секцию "I. Краткое резюме" из полного текстового
|
||||
* отчета.
|
||||
* Используется для поля summary, чтобы избежать дублирования полного текста.
|
||||
*
|
||||
* @param fullReport Полный текстовый отчет
|
||||
* @return Текст только первой секции (краткое резюме)
|
||||
*/
|
||||
private String extractSummarySection(String fullReport) {
|
||||
if (fullReport == null || fullReport.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Ищем начало первой секции "I. Краткое резюме"
|
||||
int startIndex = fullReport.indexOf("I. Краткое резюме");
|
||||
if (startIndex == -1) {
|
||||
// Если не нашли, пробуем найти просто "Краткое резюме"
|
||||
startIndex = fullReport.indexOf("Краткое резюме");
|
||||
}
|
||||
|
||||
if (startIndex == -1) {
|
||||
// Если нет секции резюме, возвращаем первые 500 символов
|
||||
return fullReport.length() > 500
|
||||
? fullReport.substring(0, 500) + "..."
|
||||
: fullReport;
|
||||
}
|
||||
|
||||
// Ищем начало второй секции "II."
|
||||
int endIndex = fullReport.indexOf("II.", startIndex + 1);
|
||||
if (endIndex == -1) {
|
||||
// Пробуем найти "II. " с пробелом
|
||||
endIndex = fullReport.indexOf("\nII. ", startIndex + 1);
|
||||
}
|
||||
|
||||
if (endIndex == -1) {
|
||||
// Если не нашли вторую секцию, возвращаем от начала до 1000 символов
|
||||
int maxLength = Math.min(startIndex + 1000, fullReport.length());
|
||||
return fullReport.substring(startIndex, maxLength).trim();
|
||||
}
|
||||
|
||||
// Извлекаем текст между I. и II.
|
||||
return fullReport.substring(startIndex, endIndex).trim();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.warn("extractSummarySection: Error extracting summary: {}", e.getMessage());
|
||||
// В случае ошибки возвращаем первые 500 символов
|
||||
return fullReport.length() > 500
|
||||
? fullReport.substring(0, 500) + "..."
|
||||
: fullReport;
|
||||
}
|
||||
}
|
||||
|
||||
private String buildMarkdownFromReport(Map<String, Object> reportData, MarketingAnalysisRequest request) {
|
||||
logger.info("buildMarkdownFromReport: Starting markdown generation for product: {}", request.getProduct());
|
||||
logger.debug("buildMarkdownFromReport: ReportData keys: {}", reportData != null ? reportData.keySet() : "null");
|
||||
@@ -3008,34 +2682,6 @@ public class MarketingAnalysisService {
|
||||
markdown.append("- **Ниша бизнеса:** ").append(request.getBusinessNiche()).append("\n");
|
||||
markdown.append("- **Продукт/услуга:** ").append(request.getProduct()).append("\n");
|
||||
|
||||
// Format target audience
|
||||
Map<String, Object> targetAudienceData = request.getTargetAudience();
|
||||
if (targetAudienceData != null) {
|
||||
StringBuilder audienceBuilder = new StringBuilder();
|
||||
if (targetAudienceData.containsKey("genders")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> genders = (List<String>) targetAudienceData.get("genders");
|
||||
if (genders != null && !genders.isEmpty()) {
|
||||
audienceBuilder.append("Гендер: ").append(String.join(", ", genders)).append(". ");
|
||||
}
|
||||
}
|
||||
if (targetAudienceData.containsKey("ageRanges")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> ageRanges = (List<String>) targetAudienceData.get("ageRanges");
|
||||
if (ageRanges != null && !ageRanges.isEmpty()) {
|
||||
audienceBuilder.append("Возраст: ").append(String.join(", ", ageRanges)).append(". ");
|
||||
}
|
||||
}
|
||||
if (targetAudienceData.containsKey("types")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> types = (List<String>) targetAudienceData.get("types");
|
||||
if (types != null && !types.isEmpty()) {
|
||||
audienceBuilder.append("Тип: ").append(String.join(", ", types)).append(". ");
|
||||
}
|
||||
}
|
||||
markdown.append("- **Целевая аудитория:** ").append(audienceBuilder.toString().trim()).append("\n");
|
||||
}
|
||||
|
||||
// Format regions
|
||||
List<String> regions = request.getRegion();
|
||||
if (regions != null && !regions.isEmpty()) {
|
||||
@@ -3063,11 +2709,6 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
markdown.append("\n");
|
||||
|
||||
// ВАЖНО: Секции "Позиционирование", "УТП", "Рекомендации по контенту" УДАЛЕНЫ,
|
||||
// так как они уже включены в fullAnalysis (генерируется методом
|
||||
// generatePolishedTextReport)
|
||||
// и вызывали дублирование контента в отчете
|
||||
|
||||
// Display full analysis - это основной блок с полным отчетом
|
||||
// Все секции (I-XI) уже включены в fullAnalysis благодаря
|
||||
// generatePolishedTextReport
|
||||
@@ -3082,11 +2723,8 @@ public class MarketingAnalysisService {
|
||||
// так как fullAnalysis уже начинается с "I. Краткое резюме"
|
||||
markdown.append(processedAnalysis).append("\n\n");
|
||||
} else {
|
||||
// Fallback to summary if fullAnalysis is not available
|
||||
String summary = (String) reportData.get("summary");
|
||||
if (summary != null && !summary.trim().isEmpty()) {
|
||||
markdown.append(summary).append("\n\n");
|
||||
}
|
||||
// Если fullAnalysis пустой, выводим сообщение об ошибке
|
||||
markdown.append("Не удалось сгенерировать анализ. Пожалуйста, попробуйте позже.\n\n");
|
||||
}
|
||||
|
||||
return markdown.toString();
|
||||
@@ -3145,14 +2783,17 @@ public class MarketingAnalysisService {
|
||||
|
||||
if (chartsData == null || chartsData.isEmpty()) {
|
||||
logger.warn("replaceChartPlaceholders: chartsData is null or empty, removing all placeholders");
|
||||
// Если данных нет, просто удаляем все плейсхолдеры
|
||||
return fullAnalysis
|
||||
// Если данных нет, удаляем все плейсхолдеры и очищаем пустые строки
|
||||
String cleaned = fullAnalysis
|
||||
.replace("[[CHART_SEASONALITY]]", "")
|
||||
.replace("[[CHART_AUDIENCE]]", "")
|
||||
.replace("[[CHART_COMPETITORS]]", "")
|
||||
.replace("[[CHART_CHANNELS]]", "")
|
||||
.replace("[[CHART_FUNNEL]]", "")
|
||||
.replace("[[CHART_SWOT]]", "");
|
||||
// Удаляем множественные пустые строки (более 2 подряд)
|
||||
cleaned = cleaned.replaceAll("(\\r?\\n){3,}", "\n\n");
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
String result = fullAnalysis;
|
||||
@@ -3190,19 +2831,26 @@ public class MarketingAnalysisService {
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Error serializing data for placeholder {} (key: {}): {}",
|
||||
placeholder, mapping.dataKey, e.getMessage(), e);
|
||||
// Удаляем плейсхолдер при ошибке сериализации
|
||||
result = result.replaceAll(regex, "");
|
||||
// Удаляем плейсхолдер при ошибке сериализации вместе с окружающими переносами
|
||||
// строк
|
||||
String placeholderWithNewlines = "(\\r?\\n)?\\s*" + regex + "\\s*(\\r?\\n)?";
|
||||
result = result.replaceAll(placeholderWithNewlines, "\n");
|
||||
} catch (Exception e) {
|
||||
logger.error("Unexpected error processing placeholder {} (key: {}): {}",
|
||||
placeholder, mapping.dataKey, e.getMessage(), e);
|
||||
// Удаляем плейсхолдер при неожиданной ошибке
|
||||
result = result.replaceAll(regex, "");
|
||||
// Удаляем плейсхолдер при неожиданной ошибке вместе с окружающими переносами
|
||||
// строк
|
||||
String placeholderWithNewlines = "(\\r?\\n)?\\s*" + regex + "\\s*(\\r?\\n)?";
|
||||
result = result.replaceAll(placeholderWithNewlines, "\n");
|
||||
}
|
||||
} else {
|
||||
logger.warn("MISSING DATA for placeholder: {} (key: {}). Removing placeholder.",
|
||||
placeholder, mapping.dataKey);
|
||||
// Если данных нет, удаляем плейсхолдер, чтобы не светить им пользователю
|
||||
result = result.replaceAll(regex, "");
|
||||
// Если данных нет, удаляем плейсхолдер и окружающие пустые строки
|
||||
// Удаляем плейсхолдер вместе с предшествующим и последующим переносом строки,
|
||||
// если они есть
|
||||
String placeholderWithNewlines = "(\\r?\\n)?\\s*" + regex + "\\s*(\\r?\\n)?";
|
||||
result = result.replaceAll(placeholderWithNewlines, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3212,6 +2860,9 @@ public class MarketingAnalysisService {
|
||||
logger.debug("replaceChartPlaceholders: Adding missing charts to sections");
|
||||
result = addMissingChartsToSections(result, chartsData, placeholderMap, processedPlaceholders);
|
||||
|
||||
// Финальная очистка: удаляем множественные пустые строки (более 2 подряд)
|
||||
result = result.replaceAll("(\\r?\\n){3,}", "\n\n");
|
||||
|
||||
logger.info(
|
||||
"replaceChartPlaceholders: Placeholder replacement completed. Processed placeholders: {}, Result length: {} chars",
|
||||
processedPlaceholders.size(), result.length());
|
||||
|
||||
Reference in New Issue
Block a user