This commit is contained in:
root
2025-12-09 22:17:32 +05:00
parent 0807280d5d
commit 7f77f48020
@@ -62,10 +62,10 @@ public class MarketingAnalysisService {
analysis.setStatus("queued"); analysis.setStatus("queued");
addStatusHistoryEntry(analysis, "queued", "Комплексный анализ создан и добавлен в очередь"); addStatusHistoryEntry(analysis, "queued", "Комплексный анализ создан и добавлен в очередь");
analysis = repository.save(analysis); analysis = repository.save(analysis);
logger.info("Unified marketing analysis created with ID: {} for user: {} with types: {}", logger.info("Unified marketing analysis created with ID: {} for user: {} with types: {}",
analysis.getId(), userId, analysisTypes); analysis.getId(), userId, analysisTypes);
return List.of(analysis); return List.of(analysis);
} }
@@ -173,25 +173,30 @@ public class MarketingAnalysisService {
// Форматирование региона // Форматирование региона
List<String> regions = request.getRegion(); List<String> regions = request.getRegion();
String regionStr = (regions != null && !regions.isEmpty()) String regionStr = (regions != null && !regions.isEmpty())
? String.join(", ", regions) ? String.join(", ", regions)
: "Не указан"; : "Не указан";
// Построение промпта №1 // Построение промпта №1
StringBuilder promptBuilder = new StringBuilder(); StringBuilder promptBuilder = new StringBuilder();
promptBuilder.append("Ты — профессиональный маркетинговый аналитик, работающий с малым и средним бизнесом в Казахстане.\n\n"); promptBuilder.append(
promptBuilder.append("Твоя задача — на основе входных данных о бизнесе пользователя выполнить маркетинговый анализ и выдать результат ТОЛЬКО в виде корректного JSON-объекта строго по указанной структуре.\n\n"); "Ты — профессиональный маркетинговый аналитик, работающий с малым и средним бизнесом в Казахстане.\n\n");
promptBuilder.append(
"Твоя задача — на основе входных данных о бизнесе пользователя выполнить маркетинговый анализ и выдать результат ТОЛЬКО в виде корректного JSON-объекта строго по указанной структуре.\n\n");
promptBuilder.append("Не добавляй пояснений, комментариев, текста до или после JSON.\n"); promptBuilder.append("Не добавляй пояснений, комментариев, текста до или после JSON.\n");
promptBuilder.append("Ответ должен быть валидным JSON.\n\n"); promptBuilder.append("Ответ должен быть валидным JSON.\n\n");
promptBuilder.append("---\n\n"); promptBuilder.append("---\n\n");
promptBuilder.append("ВХОДНЫЕ ДАННЫЕ ПОЛЬЗОВАТЕЛЯ:\n\n"); promptBuilder.append("ВХОДНЫЕ ДАННЫЕ ПОЛЬЗОВАТЕЛЯ:\n\n");
promptBuilder.append("- Название компании/продукта: ").append(request.getProduct()).append("\n"); promptBuilder.append("- Название компании/продукта: ").append(request.getProduct()).append("\n");
promptBuilder.append("- Описание продукта/услуг: ").append(request.getProduct()).append(" (").append(request.getBusinessNiche()).append(")\n"); promptBuilder.append("- Описание продукта/услуг: ").append(request.getProduct()).append(" (")
.append(request.getBusinessNiche()).append(")\n");
promptBuilder.append("- Целевая аудитория (если указана): ").append(targetAudienceStr).append("\n"); promptBuilder.append("- Целевая аудитория (если указана): ").append(targetAudienceStr).append("\n");
promptBuilder.append("- Конкуренты (если указаны): ").append(request.getBusinessNiche()).append(" (уточни основных конкурентов)\n"); promptBuilder.append("- Конкуренты (если указаны): ").append(request.getBusinessNiche())
.append(" (уточни основных конкурентов)\n");
promptBuilder.append("- Цель продвижения: ").append(request.getGoal()).append("\n"); promptBuilder.append("- Цель продвижения: ").append(request.getGoal()).append("\n");
promptBuilder.append("- Регион/город: ").append(regionStr).append("\n\n"); promptBuilder.append("- Регион/город: ").append(regionStr).append("\n\n");
promptBuilder.append("## Если каких-то данных не хватает, ты можешь логично дополнить предположениями, НО в таких полях добавь флаг \"estimated\": true.\n\n"); promptBuilder.append(
"## Если каких-то данных не хватает, ты можешь логично дополнить предположениями, НО в таких полях добавь флаг \"estimated\": true.\n\n");
promptBuilder.append("СФОРМИРУЙ ОДИН JSON СЛЕДУЮЩЕЙ СТРУКТУРЫ:\n\n"); promptBuilder.append("СФОРМИРУЙ ОДИН JSON СЛЕДУЮЩЕЙ СТРУКТУРЫ:\n\n");
promptBuilder.append("{\n"); promptBuilder.append("{\n");
promptBuilder.append("\"meta\": {\n"); promptBuilder.append("\"meta\": {\n");
@@ -233,7 +238,8 @@ public class MarketingAnalysisService {
promptBuilder.append("},\n\n"); promptBuilder.append("},\n\n");
promptBuilder.append("\"channels\": [\n"); promptBuilder.append("\"channels\": [\n");
promptBuilder.append("{\n"); promptBuilder.append("{\n");
promptBuilder.append("\"name\": string, // \"Instagram\", \"TikTok\", \"Telegram\", \"2GIS\", \"Google Ads\" и т.п.\n"); promptBuilder.append(
"\"name\": string, // \"Instagram\", \"TikTok\", \"Telegram\", \"2GIS\", \"Google Ads\" и т.п.\n");
promptBuilder.append("\"potentialIndex\": integer, // 0100\n"); promptBuilder.append("\"potentialIndex\": integer, // 0100\n");
promptBuilder.append("\"priorityLevel\": string, // \"высокий\", \"средний\", \"низкий\"\n"); promptBuilder.append("\"priorityLevel\": string, // \"высокий\", \"средний\", \"низкий\"\n");
promptBuilder.append("\"roleInStrategy\": string, // коротко: для чего этот канал\n"); promptBuilder.append("\"roleInStrategy\": string, // коротко: для чего этот канал\n");
@@ -245,7 +251,8 @@ public class MarketingAnalysisService {
promptBuilder.append("\"description\": string,\n"); promptBuilder.append("\"description\": string,\n");
promptBuilder.append("\"stages\": [\n"); promptBuilder.append("\"stages\": [\n");
promptBuilder.append("{\n"); promptBuilder.append("{\n");
promptBuilder.append("\"stageName\": string, // \"Охват\", \"Вовлечённость\", \"Клики\", \"Переходы\", \"Заявки\", \"Покупки\"\n"); promptBuilder.append(
"\"stageName\": string, // \"Охват\", \"Вовлечённость\", \"Клики\", \"Переходы\", \"Заявки\", \"Покупки\"\n");
promptBuilder.append("\"value\": integer, // количество (должно логично уменьшаться вниз по воронке)\n"); promptBuilder.append("\"value\": integer, // количество (должно логично уменьшаться вниз по воронке)\n");
promptBuilder.append("\"conversionPercent\": number // от предыдущего этапа, с одним знаком после запятой\n"); promptBuilder.append("\"conversionPercent\": number // от предыдущего этапа, с одним знаком после запятой\n");
promptBuilder.append("}\n"); promptBuilder.append("}\n");
@@ -287,7 +294,7 @@ public class MarketingAnalysisService {
String prompt = promptBuilder.toString(); String prompt = promptBuilder.toString();
String context = buildContextString(request); String context = buildContextString(request);
try { try {
String jsonResponse = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru"); String jsonResponse = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru");
if (jsonResponse == null || jsonResponse.trim().isEmpty()) { if (jsonResponse == null || jsonResponse.trim().isEmpty()) {
@@ -309,12 +316,13 @@ public class MarketingAnalysisService {
cleanedJson = cleanedJson.trim(); cleanedJson = cleanedJson.trim();
// Парсинг JSON // Парсинг JSON
Map<String, Object> jsonMap = objectMapper.readValue(cleanedJson, new TypeReference<Map<String, Object>>() {}); Map<String, Object> jsonMap = objectMapper.readValue(cleanedJson, new TypeReference<Map<String, Object>>() {
});
// Валидация JSON // Валидация JSON
if (validateJsonAnalysis(jsonMap)) { if (validateJsonAnalysis(jsonMap)) {
return jsonMap; return jsonMap;
} else { } else {
logger.warn("JSON validation failed, but returning parsed JSON anyway"); logger.warn("JSON validation failed, but returning parsed JSON anyway");
return jsonMap; return jsonMap;
} }
@@ -342,7 +350,8 @@ public class MarketingAnalysisService {
promptBuilder.append("Ты — профессиональный маркетолог и аналитик.\n\n"); promptBuilder.append("Ты — профессиональный маркетолог и аналитик.\n\n");
promptBuilder.append("Тебе передан JSON с результатами маркетингового анализа в следующей структуре:\n"); promptBuilder.append("Тебе передан JSON с результатами маркетингового анализа в следующей структуре:\n");
promptBuilder.append(jsonString).append("\n\n"); promptBuilder.append(jsonString).append("\n\n");
promptBuilder.append("На основе этих данных подготовь ПОНЯТНЫЙ для предпринимателя малого/среднего бизнеса текстовый отчёт.\n\n"); promptBuilder.append(
"На основе этих данных подготовь ПОНЯТНЫЙ для предпринимателя малого/среднего бизнеса текстовый отчёт.\n\n");
promptBuilder.append("ТРЕБОВАНИЯ К ОТЧЁТУ:\n\n"); promptBuilder.append("ТРЕБОВАНИЯ К ОТЧЁТУ:\n\n");
promptBuilder.append("1. Структура разделов:\n"); promptBuilder.append("1. Структура разделов:\n");
promptBuilder.append(" I. Краткое резюме (что за бизнес, какая цель, общий вывод)\n"); promptBuilder.append(" I. Краткое резюме (что за бизнес, какая цель, общий вывод)\n");
@@ -393,10 +402,10 @@ public class MarketingAnalysisService {
try { try {
// Проверка обязательных полей // Проверка обязательных полей
if (!jsonMap.containsKey("meta") || !jsonMap.containsKey("seasonality") || if (!jsonMap.containsKey("meta") || !jsonMap.containsKey("seasonality") ||
!jsonMap.containsKey("competitors") || !jsonMap.containsKey("audienceSegments") || !jsonMap.containsKey("competitors") || !jsonMap.containsKey("audienceSegments") ||
!jsonMap.containsKey("channels") || !jsonMap.containsKey("funnel") || !jsonMap.containsKey("channels") || !jsonMap.containsKey("funnel") ||
!jsonMap.containsKey("swot")) { !jsonMap.containsKey("swot")) {
logger.warn("Missing required fields in JSON analysis"); logger.warn("Missing required fields in JSON analysis");
return false; return false;
} }
@@ -504,7 +513,7 @@ public class MarketingAnalysisService {
*/ */
private Map<String, Object> parseChartDataFromJson(Map<String, Object> analysisJson) { private Map<String, Object> parseChartDataFromJson(Map<String, Object> analysisJson) {
Map<String, Object> chartsData = new HashMap<>(); Map<String, Object> chartsData = new HashMap<>();
if (analysisJson == null || analysisJson.isEmpty()) { if (analysisJson == null || analysisJson.isEmpty()) {
return chartsData; return chartsData;
} }
@@ -518,7 +527,7 @@ public class MarketingAnalysisService {
List<String> months = (List<String>) seasonality.get("months"); List<String> months = (List<String>) seasonality.get("months");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Object> demandIndex = (List<Object>) seasonality.get("demandIndex"); List<Object> demandIndex = (List<Object>) seasonality.get("demandIndex");
if (months != null && demandIndex != null && months.size() == demandIndex.size()) { if (months != null && demandIndex != null && months.size() == demandIndex.size()) {
Map<String, Integer> seasonalityMap = new LinkedHashMap<>(); Map<String, Integer> seasonalityMap = new LinkedHashMap<>();
for (int i = 0; i < months.size() && i < demandIndex.size(); i++) { for (int i = 0; i < months.size() && i < demandIndex.size(); i++) {
@@ -560,37 +569,37 @@ public class MarketingAnalysisService {
List<Map<String, Object>> segments = (List<Map<String, Object>>) audienceSegments.get("segments"); List<Map<String, Object>> segments = (List<Map<String, Object>>) audienceSegments.get("segments");
if (segments != null && !segments.isEmpty()) { if (segments != null && !segments.isEmpty()) {
Map<String, Object> segmentationData = new HashMap<>(); Map<String, Object> segmentationData = new HashMap<>();
// Группировка по возрасту // Группировка по возрасту
Map<String, Integer> ageGroups = new HashMap<>(); Map<String, Integer> ageGroups = new HashMap<>();
Map<String, Integer> genders = new HashMap<>(); Map<String, Integer> genders = new HashMap<>();
List<Map<String, String>> segmentsList = new ArrayList<>(); List<Map<String, String>> segmentsList = new ArrayList<>();
for (Map<String, Object> segment : segments) { for (Map<String, Object> segment : segments) {
String name = (String) segment.getOrDefault("name", "Сегмент"); String name = (String) segment.getOrDefault("name", "Сегмент");
Object percentObj = segment.get("sharePercent"); Object percentObj = segment.get("sharePercent");
int percent = percentObj instanceof Number ? ((Number) percentObj).intValue() : 0; int percent = percentObj instanceof Number ? ((Number) percentObj).intValue() : 0;
String ageRange = (String) segment.getOrDefault("ageRange", ""); String ageRange = (String) segment.getOrDefault("ageRange", "");
String gender = (String) segment.getOrDefault("gender", ""); String gender = (String) segment.getOrDefault("gender", "");
// Добавляем в общий список сегментов // Добавляем в общий список сегментов
Map<String, String> segmentData = new HashMap<>(); Map<String, String> segmentData = new HashMap<>();
segmentData.put("label", name); segmentData.put("label", name);
segmentData.put("value", String.valueOf(percent)); segmentData.put("value", String.valueOf(percent));
segmentsList.add(segmentData); segmentsList.add(segmentData);
// Группируем по возрасту // Группируем по возрасту
if (!ageRange.isEmpty()) { if (!ageRange.isEmpty()) {
ageGroups.put(ageRange, ageGroups.getOrDefault(ageRange, 0) + percent); ageGroups.put(ageRange, ageGroups.getOrDefault(ageRange, 0) + percent);
} }
// Группируем по полу // Группируем по полу
if (!gender.isEmpty()) { if (!gender.isEmpty()) {
genders.put(gender, genders.getOrDefault(gender, 0) + percent); genders.put(gender, genders.getOrDefault(gender, 0) + percent);
} }
} }
// Преобразуем в нужный формат // Преобразуем в нужный формат
List<Map<String, String>> ageGroupsList = new ArrayList<>(); List<Map<String, String>> ageGroupsList = new ArrayList<>();
for (Map.Entry<String, Integer> entry : ageGroups.entrySet()) { for (Map.Entry<String, Integer> entry : ageGroups.entrySet()) {
@@ -599,7 +608,7 @@ public class MarketingAnalysisService {
ageGroup.put("value", String.valueOf(entry.getValue())); ageGroup.put("value", String.valueOf(entry.getValue()));
ageGroupsList.add(ageGroup); ageGroupsList.add(ageGroup);
} }
List<Map<String, String>> gendersList = new ArrayList<>(); List<Map<String, String>> gendersList = new ArrayList<>();
for (Map.Entry<String, Integer> entry : genders.entrySet()) { for (Map.Entry<String, Integer> entry : genders.entrySet()) {
Map<String, String> genderData = new HashMap<>(); Map<String, String> genderData = new HashMap<>();
@@ -607,7 +616,7 @@ public class MarketingAnalysisService {
genderData.put("value", String.valueOf(entry.getValue())); genderData.put("value", String.valueOf(entry.getValue()));
gendersList.add(genderData); gendersList.add(genderData);
} }
if (!ageGroupsList.isEmpty()) { if (!ageGroupsList.isEmpty()) {
segmentationData.put("ageGroups", ageGroupsList); segmentationData.put("ageGroups", ageGroupsList);
} }
@@ -617,7 +626,7 @@ public class MarketingAnalysisService {
if (!segmentsList.isEmpty()) { if (!segmentsList.isEmpty()) {
segmentationData.put("segments", segmentsList); segmentationData.put("segments", segmentsList);
} }
if (!segmentationData.isEmpty()) { if (!segmentationData.isEmpty()) {
chartsData.put("audienceSegmentation", segmentationData); chartsData.put("audienceSegmentation", segmentationData);
} }
@@ -651,11 +660,11 @@ public class MarketingAnalysisService {
Map<String, Object> stageData = new HashMap<>(); Map<String, Object> stageData = new HashMap<>();
stageData.put("stage", stage.getOrDefault("stageName", "Этап")); stageData.put("stage", stage.getOrDefault("stageName", "Этап"));
stageData.put("value", stage.getOrDefault("value", 0)); stageData.put("value", stage.getOrDefault("value", 0));
Object conversionObj = stage.get("conversionPercent"); Object conversionObj = stage.get("conversionPercent");
if (conversionObj instanceof Number) { if (conversionObj instanceof Number) {
stageData.put("conversion", ((Number) conversionObj).intValue()); stageData.put("conversion", ((Number) conversionObj).intValue());
} else { } else {
stageData.put("conversion", 0); stageData.put("conversion", 0);
} }
funnelList.add(stageData); funnelList.add(stageData);
@@ -688,7 +697,7 @@ public class MarketingAnalysisService {
} }
DetailLevel detailLevel = request.getDetailLevelEnum(); DetailLevel detailLevel = request.getDetailLevelEnum();
String detailLevelName = detailLevel != null ? detailLevel.getDisplayName() : "Стандартно"; String detailLevelName = detailLevel != null ? detailLevel.getDisplayName() : "Стандартно";
// Build list of analysis type names for display // Build list of analysis type names for display
List<String> analysisTypeNames = analysisTypes.stream() List<String> analysisTypeNames = analysisTypes.stream()
.map(AnalysisType::getDisplayName) .map(AnalysisType::getDisplayName)
@@ -726,7 +735,7 @@ public class MarketingAnalysisService {
} }
contextBuilder.append("Целевая аудитория: ").append(audienceBuilder.toString().trim()).append("\n"); contextBuilder.append("Целевая аудитория: ").append(audienceBuilder.toString().trim()).append("\n");
} }
// Format regions // Format regions
List<String> regions = request.getRegion(); List<String> regions = request.getRegion();
if (regions != null && !regions.isEmpty()) { if (regions != null && !regions.isEmpty()) {
@@ -745,7 +754,7 @@ public class MarketingAnalysisService {
// Шаг 1: Генерация JSON-анализа (Промпт №1) // Шаг 1: Генерация JSON-анализа (Промпт №1)
Map<String, Object> analysisJson = generateJsonAnalysis(request); Map<String, Object> analysisJson = generateJsonAnalysis(request);
if (analysisJson == null || analysisJson.isEmpty()) { if (analysisJson == null || analysisJson.isEmpty()) {
logger.error("Failed to generate JSON analysis"); logger.error("Failed to generate JSON analysis");
// При ошибке возвращаем базовую структуру с сообщением об ошибке // При ошибке возвращаем базовую структуру с сообщением об ошибке
@@ -757,19 +766,21 @@ public class MarketingAnalysisService {
} else { } else {
// Сохраняем JSON в отчёт // Сохраняем JSON в отчёт
report.put("analysisJson", analysisJson); report.put("analysisJson", analysisJson);
// Шаг 2: Генерация текстового отчёта на основе JSON (Промпт №2) // Шаг 2: Генерация текстового отчёта на основе JSON (Промпт №2)
String textReport = generateTextReportFromJson(analysisJson); String textReport = generateTextReportFromJson(analysisJson);
// Извлекаем данные для графиков из JSON // Извлекаем данные для графиков из JSON
Map<String, Object> chartsData = parseChartDataFromJson(analysisJson); Map<String, Object> chartsData = parseChartDataFromJson(analysisJson);
report.put("chartsData", chartsData); report.put("chartsData", chartsData);
// Сохраняем данные анализа // Сохраняем данные анализа
report.put("analysisTypes", analysisTypeNames); report.put("analysisTypes", analysisTypeNames);
report.put("detailLevel", detailLevelName); report.put("detailLevel", detailLevelName);
report.put("fullAnalysis", textReport != null ? textReport : "Анализ не удалось сгенерировать."); report.put("fullAnalysis", textReport != null ? textReport : "Анализ не удалось сгенерировать.");
report.put("summary", textReport != null ? textReport : "Анализ не удалось сгенерировать."); // Keep for backward compatibility report.put("summary", textReport != null ? textReport : "Анализ не удалось сгенерировать."); // Keep for
// backward
// compatibility
} }
// Generate target audience description (adjusted by detail level) // Generate target audience description (adjusted by detail level)
@@ -1072,7 +1083,7 @@ public class MarketingAnalysisService {
Map<String, Object> chartsData = (Map<String, Object>) reportData.get("chartsData"); Map<String, Object> chartsData = (Map<String, Object>) reportData.get("chartsData");
if (chartsData != null && !chartsData.isEmpty()) { if (chartsData != null && !chartsData.isEmpty()) {
markdown.append("## 📊 Числовые данные для графиков\n\n"); markdown.append("## 📊 Числовые данные для графиков\n\n");
// Seasonality table // Seasonality table
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Integer> seasonality = (Map<String, Integer>) chartsData.get("seasonality"); Map<String, Integer> seasonality = (Map<String, Integer>) chartsData.get("seasonality");
@@ -1085,14 +1096,16 @@ public class MarketingAnalysisService {
} }
markdown.append("\n"); markdown.append("\n");
} }
// Competitors table // Competitors table
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Map<String, Object>> competitors = (List<Map<String, Object>>) chartsData.get("competitors"); List<Map<String, Object>> competitors = (List<Map<String, Object>>) chartsData.get("competitors");
if (competitors != null && !competitors.isEmpty()) { if (competitors != null && !competitors.isEmpty()) {
markdown.append("### 2️⃣ Сравнительный анализ конкурентов\n\n"); markdown.append("### 2️⃣ Сравнительный анализ конкурентов\n\n");
markdown.append("| Конкурент | Охват аудитории | Активность в соцсетях (0–100) | Количество отзывов | Сильные стороны | Слабые стороны |\n"); markdown.append(
markdown.append("|-----------|-----------------|-------------------------------|-------------------|----------------|----------------|\n"); "| Конкурент | Охват аудитории | Активность в соцсетях (0–100) | Количество отзывов | Сильные стороны | Слабые стороны |\n");
markdown.append(
"|-----------|-----------------|-------------------------------|-------------------|----------------|----------------|\n");
for (Map<String, Object> comp : competitors) { for (Map<String, Object> comp : competitors) {
markdown.append("| ").append(comp.get("name")).append(" | ") markdown.append("| ").append(comp.get("name")).append(" | ")
.append(comp.get("reach")).append(" | ") .append(comp.get("reach")).append(" | ")
@@ -1103,13 +1116,13 @@ public class MarketingAnalysisService {
} }
markdown.append("\n"); markdown.append("\n");
} }
// Audience segmentation tables // Audience segmentation tables
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> audienceSegmentation = (Map<String, Object>) chartsData.get("audienceSegmentation"); Map<String, Object> audienceSegmentation = (Map<String, Object>) chartsData.get("audienceSegmentation");
if (audienceSegmentation != null && !audienceSegmentation.isEmpty()) { if (audienceSegmentation != null && !audienceSegmentation.isEmpty()) {
markdown.append("### 3️⃣ Сегментация целевой аудитории\n\n"); markdown.append("### 3️⃣ Сегментация целевой аудитории\n\n");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Map<String, String>> ageGroups = (List<Map<String, String>>) audienceSegmentation.get("ageGroups"); List<Map<String, String>> ageGroups = (List<Map<String, String>>) audienceSegmentation.get("ageGroups");
if (ageGroups != null && !ageGroups.isEmpty()) { if (ageGroups != null && !ageGroups.isEmpty()) {
@@ -1117,11 +1130,12 @@ public class MarketingAnalysisService {
markdown.append("| Возрастная группа | Процент (%) |\n"); markdown.append("| Возрастная группа | Процент (%) |\n");
markdown.append("|-------------------|-------------|\n"); markdown.append("|-------------------|-------------|\n");
for (Map<String, String> group : ageGroups) { for (Map<String, String> group : ageGroups) {
markdown.append("| ").append(group.get("label")).append(" | ").append(group.get("value")).append(" |\n"); markdown.append("| ").append(group.get("label")).append(" | ").append(group.get("value"))
.append(" |\n");
} }
markdown.append("\n"); markdown.append("\n");
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Map<String, String>> genders = (List<Map<String, String>>) audienceSegmentation.get("genders"); List<Map<String, String>> genders = (List<Map<String, String>>) audienceSegmentation.get("genders");
if (genders != null && !genders.isEmpty()) { if (genders != null && !genders.isEmpty()) {
@@ -1129,11 +1143,12 @@ public class MarketingAnalysisService {
markdown.append("| Пол | Процент (%) |\n"); markdown.append("| Пол | Процент (%) |\n");
markdown.append("|-----|-------------|\n"); markdown.append("|-----|-------------|\n");
for (Map<String, String> gender : genders) { for (Map<String, String> gender : genders) {
markdown.append("| ").append(gender.get("label")).append(" | ").append(gender.get("value")).append(" |\n"); markdown.append("| ").append(gender.get("label")).append(" | ").append(gender.get("value"))
.append(" |\n");
} }
markdown.append("\n"); markdown.append("\n");
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Map<String, String>> segments = (List<Map<String, String>>) audienceSegmentation.get("segments"); List<Map<String, String>> segments = (List<Map<String, String>>) audienceSegmentation.get("segments");
if (segments != null && !segments.isEmpty()) { if (segments != null && !segments.isEmpty()) {
@@ -1141,15 +1156,17 @@ public class MarketingAnalysisService {
markdown.append("| Сегмент | Процент (%) |\n"); markdown.append("| Сегмент | Процент (%) |\n");
markdown.append("|---------|-------------|\n"); markdown.append("|---------|-------------|\n");
for (Map<String, String> segment : segments) { for (Map<String, String> segment : segments) {
markdown.append("| ").append(segment.get("label")).append(" | ").append(segment.get("value")).append(" |\n"); markdown.append("| ").append(segment.get("label")).append(" | ").append(segment.get("value"))
.append(" |\n");
} }
markdown.append("\n"); markdown.append("\n");
} }
} }
// Channels potential table // Channels potential table
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Map<String, Object>> channelsPotential = (List<Map<String, Object>>) chartsData.get("channelsPotential"); List<Map<String, Object>> channelsPotential = (List<Map<String, Object>>) chartsData
.get("channelsPotential");
if (channelsPotential != null && !channelsPotential.isEmpty()) { if (channelsPotential != null && !channelsPotential.isEmpty()) {
markdown.append("### 4️⃣ Потенциал каналов продвижения\n\n"); markdown.append("### 4️⃣ Потенциал каналов продвижения\n\n");
markdown.append("| Канал | Потенциал (0–100) | Обоснование |\n"); markdown.append("| Канал | Потенциал (0–100) | Обоснование |\n");
@@ -1161,7 +1178,7 @@ public class MarketingAnalysisService {
} }
markdown.append("\n"); markdown.append("\n");
} }
// Conversion funnel table // Conversion funnel table
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Map<String, Object>> conversionFunnel = (List<Map<String, Object>>) chartsData.get("conversionFunnel"); List<Map<String, Object>> conversionFunnel = (List<Map<String, Object>>) chartsData.get("conversionFunnel");
@@ -1273,7 +1290,8 @@ public class MarketingAnalysisService {
// Content Recommendations section // Content Recommendations section
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, Object> contentRecommendations = (Map<String, Object>) analysisJson.get("contentRecommendations"); Map<String, Object> contentRecommendations = (Map<String, Object>) analysisJson
.get("contentRecommendations");
if (contentRecommendations != null) { if (contentRecommendations != null) {
markdown.append("## Рекомендации по контенту\n\n"); markdown.append("## Рекомендации по контенту\n\n");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -1322,10 +1340,10 @@ public class MarketingAnalysisService {
markdown.append(fullAnalysis).append("\n\n"); markdown.append(fullAnalysis).append("\n\n");
} else { } else {
// Fallback to summary if fullAnalysis is not available // Fallback to summary if fullAnalysis is not available
String summary = (String) reportData.get("summary"); String summary = (String) reportData.get("summary");
if (summary != null && !summary.trim().isEmpty()) { if (summary != null && !summary.trim().isEmpty()) {
markdown.append("## Комплексный маркетинговый анализ\n\n"); markdown.append("## Комплексный маркетинговый анализ\n\n");
markdown.append(summary).append("\n\n"); markdown.append(summary).append("\n\n");
} }
} }
@@ -1429,7 +1447,7 @@ public class MarketingAnalysisService {
// Set summary (backward compatibility) // Set summary (backward compatibility)
report.setSummary((String) reportData.get("summary")); report.setSummary((String) reportData.get("summary"));
// Set full analysis (new field) // Set full analysis (new field)
String fullAnalysis = (String) reportData.get("fullAnalysis"); String fullAnalysis = (String) reportData.get("fullAnalysis");
if (fullAnalysis != null) { if (fullAnalysis != null) {