This commit is contained in:
root
2025-12-07 18:06:56 +05:00
parent 4d50cd3da5
commit 33f65f7fca
3 changed files with 892 additions and 49 deletions
@@ -2,6 +2,7 @@ package kz.konturai.parser.dto;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
public class MarketingAnalysisResult {
private String analysisId;
@@ -64,6 +65,8 @@ public class MarketingAnalysisResult {
public static class MarketingReport {
private String summary;
private String fullAnalysis;
private Map<String, Object> chartsData;
private TargetAudience targetAudience;
private List<String> recommendations;
private Strategy strategy;
@@ -80,6 +83,22 @@ public class MarketingAnalysisResult {
this.summary = summary;
}
public String getFullAnalysis() {
return fullAnalysis;
}
public void setFullAnalysis(String fullAnalysis) {
this.fullAnalysis = fullAnalysis;
}
public Map<String, Object> getChartsData() {
return chartsData;
}
public void setChartsData(Map<String, Object> chartsData) {
this.chartsData = chartsData;
}
public TargetAudience getTargetAudience() {
return targetAudience;
}
@@ -197,13 +197,18 @@ public class MarketingAnalysisService {
contextBuilder.append("Типы анализа: ").append(String.join(", ", analysisTypeNames)).append("\n");
String context = contextBuilder.toString();
// Generate unified analysis with all types
String unifiedAnalysisContent = generateUnifiedAnalysis(context, analysisTypes, detailLevel);
// Generate chart data first (numerical data for graphs)
Map<String, Object> chartsData = generateChartData(context, request);
report.put("chartsData", chartsData);
// Generate unified analysis with all types using new prompt structure
String unifiedAnalysisContent = generateUnifiedAnalysis(context, request, detailLevel);
// Store analysis data
report.put("analysisTypes", analysisTypeNames);
report.put("detailLevel", detailLevelName);
report.put("summary", unifiedAnalysisContent != null ? unifiedAnalysisContent : "Анализ не удалось сгенерировать.");
report.put("fullAnalysis", unifiedAnalysisContent != null ? unifiedAnalysisContent : "Анализ не удалось сгенерировать.");
report.put("summary", unifiedAnalysisContent != null ? unifiedAnalysisContent : "Анализ не удалось сгенерировать."); // Keep for backward compatibility
// Generate target audience description (adjusted by detail level)
String audiencePrompt;
@@ -290,10 +295,474 @@ public class MarketingAnalysisService {
return report;
}
private String generateUnifiedAnalysis(String context, List<AnalysisType> analysisTypes, DetailLevel detailLevel) {
if (analysisTypes == null || analysisTypes.isEmpty()) {
analysisTypes = List.of(AnalysisType.РЫНОК); // Default
private Map<String, Object> generateChartData(String context, MarketingAnalysisRequest request) {
Map<String, Object> chartsData = new HashMap<>();
// Build prompt for chart data generation
StringBuilder chartPromptBuilder = new StringBuilder();
chartPromptBuilder.append("Ты — профессиональный маркетолог и аналитик, работающий с малым и средним бизнесом Казахстана. ");
chartPromptBuilder.append("На основе следующей информации о бизнесе сгенерируй ТОЛЬКО числовые данные в виде таблиц для построения графиков. ");
chartPromptBuilder.append("Ответ должен содержать ТОЛЬКО таблицы в формате Markdown, без дополнительного текста.\n\n");
chartPromptBuilder.append("📊 Сгенерируй следующие таблицы:\n\n");
chartPromptBuilder.append("1️⃣ Сезонность спроса (линейный график, 12 месяцев)\n");
chartPromptBuilder.append("Выведи таблицу в формате:\n");
chartPromptBuilder.append("| Месяц | Уровень спроса (0–100) |\n");
chartPromptBuilder.append("|-------|------------------------|\n");
chartPromptBuilder.append("| Январь | [число 0-100] |\n");
chartPromptBuilder.append("| Февраль | [число 0-100] |\n");
chartPromptBuilder.append("... (для всех 12 месяцев)\n");
chartPromptBuilder.append("Значения должны соответствовать нише, иметь сезонную динамику, быть реалистичными.\n\n");
chartPromptBuilder.append("2️⃣ Сравнительный анализ конкурентов (столбчатая диаграмма)\n");
chartPromptBuilder.append("Выведи таблицу в формате:\n");
chartPromptBuilder.append("| Конкурент | Охват аудитории | Активность в соцсетях (0–100) | Количество отзывов | Сильные стороны | Слабые стороны |\n");
chartPromptBuilder.append("|-----------|-----------------|-------------------------------|-------------------|----------------|----------------|\n");
chartPromptBuilder.append("| [Название] | [1000-50000] | [0-100] | [число] | [текст] | [текст] |\n");
chartPromptBuilder.append("... (для 3-5 конкурентов)\n");
chartPromptBuilder.append("Охват — в диапазоне 1 000–50 000, Активность — 0–100.\n\n");
chartPromptBuilder.append("3️⃣ Сегментация целевой аудитории (круговая диаграмма)\n");
chartPromptBuilder.append("Выведи три таблицы:\n");
chartPromptBuilder.append("Таблица 1 - Возрастные группы:\n");
chartPromptBuilder.append("| Возрастная группа | Процент (%) |\n");
chartPromptBuilder.append("|-------------------|-------------|\n");
chartPromptBuilder.append("| [группа] | [число] |\n\n");
chartPromptBuilder.append("Таблица 2 - Пол:\n");
chartPromptBuilder.append("| Пол | Процент (%) |\n");
chartPromptBuilder.append("|-----|-------------|\n");
chartPromptBuilder.append("| [пол] | [число] |\n\n");
chartPromptBuilder.append("Таблица 3 - Сегменты ЦА:\n");
chartPromptBuilder.append("| Сегмент | Процент (%) |\n");
chartPromptBuilder.append("|---------|-------------|\n");
chartPromptBuilder.append("| [сегмент] | [число] |\n");
chartPromptBuilder.append("Сумма процентов в каждой таблице = 100%.\n\n");
chartPromptBuilder.append("4️⃣ Потенциал каналов продвижения (бар-чарт)\n");
chartPromptBuilder.append("Выведи таблицу в формате:\n");
chartPromptBuilder.append("| Канал | Потенциал (0–100) | Обоснование |\n");
chartPromptBuilder.append("|-------|-------------------|-------------|\n");
chartPromptBuilder.append("| Instagram | [0-100] | [текст] |\n");
chartPromptBuilder.append("| TikTok | [0-100] | [текст] |\n");
chartPromptBuilder.append("| Telegram | [0-100] | [текст] |\n");
chartPromptBuilder.append("| 2GIS | [0-100] | [текст] |\n");
chartPromptBuilder.append("| Google Ads | [0-100] | [текст] |\n\n");
chartPromptBuilder.append("5️⃣ Конверсионная воронка (воронка спроса)\n");
chartPromptBuilder.append("Выведи таблицу в формате:\n");
chartPromptBuilder.append("| Этап | Значение | Конверсия (%) |\n");
chartPromptBuilder.append("|------|----------|---------------|\n");
chartPromptBuilder.append("| Охват | [число] | 100 |\n");
chartPromptBuilder.append("| Вовлечённость | [число] | [%] |\n");
chartPromptBuilder.append("| Клики | [число] | [%] |\n");
chartPromptBuilder.append("| Переходы | [число] | [%] |\n");
chartPromptBuilder.append("| Заявки | [число] | [%] |\n");
chartPromptBuilder.append("| Покупки | [число] | [%] |\n");
chartPromptBuilder.append("Числа должны уменьшаться логично от этапа к этапу.\n\n");
chartPromptBuilder.append("Информация о бизнесе:\n");
chartPromptBuilder.append(context);
String chartPrompt = chartPromptBuilder.toString();
String chartDataResponse = openAIAnalyticsService.generateWithInstruction(context, chartPrompt, "ru");
// Parse the response to extract chart data
if (chartDataResponse != null && !chartDataResponse.trim().isEmpty()) {
Map<String, Object> parsedData = parseChartDataFromResponse(chartDataResponse);
chartsData.putAll(parsedData);
}
return chartsData;
}
private Map<String, Object> parseChartDataFromResponse(String response) {
Map<String, Object> chartData = new HashMap<>();
// Parse seasonality data
Map<String, Integer> seasonality = parseSeasonalityData(response);
if (!seasonality.isEmpty()) {
chartData.put("seasonality", seasonality);
}
// Parse competitors data
List<Map<String, Object>> competitors = parseCompetitorsData(response);
if (!competitors.isEmpty()) {
chartData.put("competitors", competitors);
}
// Parse audience segmentation
Map<String, Object> audienceSegmentation = parseAudienceSegmentation(response);
if (!audienceSegmentation.isEmpty()) {
chartData.put("audienceSegmentation", audienceSegmentation);
}
// Parse channels potential
List<Map<String, Object>> channelsPotential = parseChannelsPotential(response);
if (!channelsPotential.isEmpty()) {
chartData.put("channelsPotential", channelsPotential);
}
// Parse conversion funnel
List<Map<String, Object>> conversionFunnel = parseConversionFunnel(response);
if (!conversionFunnel.isEmpty()) {
chartData.put("conversionFunnel", conversionFunnel);
}
return chartData;
}
private Map<String, Integer> parseSeasonalityData(String response) {
Map<String, Integer> seasonality = new LinkedHashMap<>();
String[] months = {"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь",
"Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"};
// Try to find table with seasonality data
String[] lines = response.split("\n");
boolean inTable = false;
for (String line : lines) {
line = line.trim();
if (line.contains("Месяц") && line.contains("Уровень спроса")) {
inTable = true;
continue;
}
if (inTable && line.startsWith("|") && !line.startsWith("|---")) {
String[] parts = line.split("\\|");
if (parts.length >= 3) {
String month = parts[1].trim();
String valueStr = parts[2].trim();
try {
// Extract number from value string
valueStr = valueStr.replaceAll("[^0-9]", "");
if (!valueStr.isEmpty()) {
int value = Integer.parseInt(valueStr);
if (value >= 0 && value <= 100) {
seasonality.put(month, value);
}
}
} catch (NumberFormatException e) {
// Skip invalid values
}
}
}
if (inTable && line.isEmpty()) {
break;
}
}
// If parsing failed, generate default values
if (seasonality.isEmpty()) {
for (String month : months) {
seasonality.put(month, 50 + (int)(Math.random() * 30)); // Random between 50-80
}
}
return seasonality;
}
private List<Map<String, Object>> parseCompetitorsData(String response) {
List<Map<String, Object>> competitors = new ArrayList<>();
String[] lines = response.split("\n");
boolean inTable = false;
for (String line : lines) {
line = line.trim();
if (line.contains("Конкурент") && line.contains("Охват аудитории")) {
inTable = true;
continue;
}
if (inTable && line.startsWith("|") && !line.startsWith("|---")) {
String[] parts = line.split("\\|");
if (parts.length >= 7) {
Map<String, Object> competitor = new HashMap<>();
competitor.put("name", parts[1].trim());
try {
String reachStr = parts[2].trim().replaceAll("[^0-9]", "");
if (!reachStr.isEmpty()) {
competitor.put("reach", Integer.parseInt(reachStr));
} else {
competitor.put("reach", 10000);
}
} catch (NumberFormatException e) {
competitor.put("reach", 10000);
}
try {
String activityStr = parts[3].trim().replaceAll("[^0-9]", "");
if (!activityStr.isEmpty()) {
int activity = Integer.parseInt(activityStr);
competitor.put("activity", Math.min(100, Math.max(0, activity)));
} else {
competitor.put("activity", 50);
}
} catch (NumberFormatException e) {
competitor.put("activity", 50);
}
try {
String reviewsStr = parts[4].trim().replaceAll("[^0-9]", "");
if (!reviewsStr.isEmpty()) {
competitor.put("reviews", Integer.parseInt(reviewsStr));
} else {
competitor.put("reviews", 100);
}
} catch (NumberFormatException e) {
competitor.put("reviews", 100);
}
competitor.put("strengths", parts[5].trim());
competitor.put("weaknesses", parts[6].trim());
if (!competitor.get("name").toString().isEmpty()) {
competitors.add(competitor);
}
}
}
if (inTable && line.isEmpty() && !competitors.isEmpty()) {
break;
}
}
// If parsing failed, add default competitor
if (competitors.isEmpty()) {
Map<String, Object> defaultCompetitor = new HashMap<>();
defaultCompetitor.put("name", "Основной конкурент");
defaultCompetitor.put("reach", 15000);
defaultCompetitor.put("activity", 65);
defaultCompetitor.put("reviews", 150);
defaultCompetitor.put("strengths", "Сильное присутствие в соцсетях");
defaultCompetitor.put("weaknesses", "Высокая цена");
competitors.add(defaultCompetitor);
}
return competitors;
}
private Map<String, Object> parseAudienceSegmentation(String response) {
Map<String, Object> segmentation = new HashMap<>();
List<Map<String, String>> ageGroups = new ArrayList<>();
List<Map<String, String>> genders = new ArrayList<>();
List<Map<String, String>> segments = new ArrayList<>();
String[] lines = response.split("\n");
boolean inAgeTable = false;
boolean inGenderTable = false;
boolean inSegmentsTable = false;
for (String line : lines) {
line = line.trim();
if (line.contains("Возрастная группа") || line.contains("Возраст")) {
inAgeTable = true;
inGenderTable = false;
inSegmentsTable = false;
continue;
}
if (line.contains("Пол") && !line.contains("Возраст")) {
inAgeTable = false;
inGenderTable = true;
inSegmentsTable = false;
continue;
}
if (line.contains("Сегмент") || (line.contains("Сегменты") && line.contains("ЦА"))) {
inAgeTable = false;
inGenderTable = false;
inSegmentsTable = true;
continue;
}
if (line.startsWith("|") && !line.startsWith("|---")) {
String[] parts = line.split("\\|");
if (parts.length >= 3) {
String name = parts[1].trim();
String percentStr = parts[2].trim().replaceAll("[^0-9]", "");
if (!name.isEmpty() && !percentStr.isEmpty()) {
try {
int percent = Integer.parseInt(percentStr);
Map<String, String> item = new HashMap<>();
item.put("label", name);
item.put("value", String.valueOf(percent));
if (inAgeTable) {
ageGroups.add(item);
} else if (inGenderTable) {
genders.add(item);
} else if (inSegmentsTable) {
segments.add(item);
}
} catch (NumberFormatException e) {
// Skip invalid values
}
}
}
}
}
if (!ageGroups.isEmpty()) {
segmentation.put("ageGroups", ageGroups);
}
if (!genders.isEmpty()) {
segmentation.put("genders", genders);
}
if (!segments.isEmpty()) {
segmentation.put("segments", segments);
}
// Add defaults if parsing failed
if (segmentation.isEmpty()) {
List<Map<String, String>> defaultAgeGroups = new ArrayList<>();
defaultAgeGroups.add(Map.of("label", "18-25", "value", "25"));
defaultAgeGroups.add(Map.of("label", "26-35", "value", "35"));
defaultAgeGroups.add(Map.of("label", "36-45", "value", "30"));
defaultAgeGroups.add(Map.of("label", "46+", "value", "10"));
segmentation.put("ageGroups", defaultAgeGroups);
List<Map<String, String>> defaultGenders = new ArrayList<>();
defaultGenders.add(Map.of("label", "Мужской", "value", "55"));
defaultGenders.add(Map.of("label", "Женский", "value", "45"));
segmentation.put("genders", defaultGenders);
}
return segmentation;
}
private List<Map<String, Object>> parseChannelsPotential(String response) {
List<Map<String, Object>> channels = new ArrayList<>();
String[] channelNames = {"Instagram", "TikTok", "Telegram", "2GIS", "Google Ads"};
String[] lines = response.split("\n");
boolean inTable = false;
for (String line : lines) {
line = line.trim();
if (line.contains("Канал") && line.contains("Потенциал")) {
inTable = true;
continue;
}
if (inTable && line.startsWith("|") && !line.startsWith("|---")) {
String[] parts = line.split("\\|");
if (parts.length >= 4) {
String channelName = parts[1].trim();
String potentialStr = parts[2].trim().replaceAll("[^0-9]", "");
String justification = parts[3].trim();
if (!channelName.isEmpty()) {
Map<String, Object> channel = new HashMap<>();
channel.put("name", channelName);
try {
if (!potentialStr.isEmpty()) {
int potential = Integer.parseInt(potentialStr);
channel.put("potential", Math.min(100, Math.max(0, potential)));
} else {
channel.put("potential", 50);
}
} catch (NumberFormatException e) {
channel.put("potential", 50);
}
channel.put("justification", justification.isEmpty() ? "Подходит для данной ниши" : justification);
channels.add(channel);
}
}
}
if (inTable && line.isEmpty() && !channels.isEmpty()) {
break;
}
}
// If parsing failed, add default channels
if (channels.isEmpty()) {
for (String channelName : channelNames) {
Map<String, Object> channel = new HashMap<>();
channel.put("name", channelName);
channel.put("potential", 50 + (int)(Math.random() * 40)); // Random between 50-90
channel.put("justification", "Подходит для данной ниши");
channels.add(channel);
}
}
return channels;
}
private List<Map<String, Object>> parseConversionFunnel(String response) {
List<Map<String, Object>> funnel = new ArrayList<>();
String[] stages = {"Охват", "Вовлечённость", "Клики", "Переходы", "Заявки", "Покупки"};
String[] lines = response.split("\n");
boolean inTable = false;
for (String line : lines) {
line = line.trim();
if (line.contains("Этап") && line.contains("Значение")) {
inTable = true;
continue;
}
if (inTable && line.startsWith("|") && !line.startsWith("|---")) {
String[] parts = line.split("\\|");
if (parts.length >= 4) {
String stage = parts[1].trim();
String valueStr = parts[2].trim().replaceAll("[^0-9]", "");
String conversionStr = parts[3].trim().replaceAll("[^0-9]", "");
if (!stage.isEmpty()) {
Map<String, Object> funnelStage = new HashMap<>();
funnelStage.put("stage", stage);
try {
if (!valueStr.isEmpty()) {
funnelStage.put("value", Integer.parseInt(valueStr));
} else {
// Generate default decreasing values
int baseValue = 10000;
int index = funnel.size();
funnelStage.put("value", (int)(baseValue * Math.pow(0.6, index)));
}
} catch (NumberFormatException e) {
int baseValue = 10000;
int index = funnel.size();
funnelStage.put("value", (int)(baseValue * Math.pow(0.6, index)));
}
try {
if (!conversionStr.isEmpty()) {
funnelStage.put("conversion", Integer.parseInt(conversionStr));
} else {
// Default conversion rates
int[] defaultConversions = {100, 40, 25, 15, 8, 3};
funnelStage.put("conversion", defaultConversions[Math.min(funnel.size(), defaultConversions.length - 1)]);
}
} catch (NumberFormatException e) {
int[] defaultConversions = {100, 40, 25, 15, 8, 3};
funnelStage.put("conversion", defaultConversions[Math.min(funnel.size(), defaultConversions.length - 1)]);
}
funnel.add(funnelStage);
}
}
}
if (inTable && line.isEmpty() && !funnel.isEmpty()) {
break;
}
}
// If parsing failed, add default funnel
if (funnel.isEmpty()) {
int baseValue = 10000;
int[] conversions = {100, 40, 25, 15, 8, 3};
for (int i = 0; i < stages.length; i++) {
Map<String, Object> funnelStage = new HashMap<>();
funnelStage.put("stage", stages[i]);
funnelStage.put("value", (int)(baseValue * Math.pow(0.6, i)));
funnelStage.put("conversion", conversions[i]);
funnel.add(funnelStage);
}
}
return funnel;
}
private String generateUnifiedAnalysis(String context, MarketingAnalysisRequest request, DetailLevel detailLevel) {
if (detailLevel == null) {
detailLevel = DetailLevel.СТАНДАРТНО; // Default
}
@@ -308,52 +777,158 @@ public class MarketingAnalysisService {
lengthInstruction = "Каждый раздел должен быть структурированным (3-5 абзацев).";
}
StringBuilder promptBuilder = new StringBuilder();
promptBuilder.append("Проведи комплексный маркетинговый анализ на основе предоставленной информации о бизнесе. ");
promptBuilder.append("Анализ должен быть качественным, прикладным к конкретной тематике и задаче.\n\n");
// Build input data section
StringBuilder inputDataBuilder = new StringBuilder();
inputDataBuilder.append("Название компании/продукта: ").append(request.getProduct()).append("\n");
inputDataBuilder.append("Описание продукта: ").append(request.getProduct()).append("\n");
promptBuilder.append("Включи следующие разделы анализа:\n\n");
// Add section for each analysis type
int sectionNumber = 1;
for (AnalysisType analysisType : analysisTypes) {
promptBuilder.append(sectionNumber).append(". ");
switch (analysisType) {
case РЫНОК:
promptBuilder.append("Анализ рынка: ");
promptBuilder.append("Включи размер рынка, динамику роста, основные сегменты, тренды и перспективы развития.\n");
break;
case КОНКУРЕНТЫ:
promptBuilder.append("Анализ конкурентов: ");
promptBuilder.append("Включи основных конкурентов, их сильные и слабые стороны, позиционирование, ");
promptBuilder.append("ценовую политику и маркетинговые стратегии.\n");
break;
case ЦА:
promptBuilder.append("Анализ целевой аудитории: ");
promptBuilder.append("Включи демографические характеристики, психографический профиль, потребности и боли, ");
promptBuilder.append("поведенческие паттерны и предпочтения.\n");
break;
case КАНАЛЫ:
promptBuilder.append("Анализ маркетинговых каналов: ");
promptBuilder.append("Включи оценку эффективности различных каналов коммуникации, рекомендации по выбору каналов, ");
promptBuilder.append("особенности использования каждого канала и бюджетные рекомендации.\n");
break;
case SWOT:
promptBuilder.append("SWOT-анализ: ");
promptBuilder.append("Включи анализ Сильных сторон (Strengths), Слабых сторон (Weaknesses), ");
promptBuilder.append("Возможностей (Opportunities) и Угроз (Threats). ");
promptBuilder.append("Ответ должен быть структурированным с четким разделением по каждому разделу SWOT.\n");
break;
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(". ");
}
}
sectionNumber++;
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(". ");
}
}
inputDataBuilder.append("Целевая аудитория: ").append(audienceBuilder.toString().trim()).append("\n");
}
inputDataBuilder.append("Конкуренты: ").append(request.getBusinessNiche()).append(" (уточни основных конкурентов)\n");
inputDataBuilder.append("Цель продвижения: ").append(request.getGoal()).append("\n");
List<String> regions = request.getRegion();
if (regions != null && !regions.isEmpty()) {
inputDataBuilder.append("Регион/город: ").append(String.join(", ", regions)).append("\n");
}
promptBuilder.append("\n");
promptBuilder.append(sectionNumber).append(". Общий вывод: ");
promptBuilder.append("Объедини все разделы анализа в единый комплексный вывод, ");
promptBuilder.append("выдели ключевые возможности и особенности бизнеса, ");
promptBuilder.append("сформулируй прикладные рекомендации, применимые к конкретной тематике и задаче.\n\n");
// Build the new prompt according to documentation
StringBuilder promptBuilder = new StringBuilder();
promptBuilder.append("Ты — профессиональный маркетолог и аналитик, работающий с малым и средним бизнесом Казахстана. ");
promptBuilder.append("Твоя задача — выполнить глубокий маркетинговый анализ компании пользователя с генерацией всех необходимых количественных данных для построения графиков и диаграмм. ");
promptBuilder.append("Пиши структурированно, лаконично, без воды, понятным языком, ориентируясь на предпринимателя МСБ.\n\n");
promptBuilder.append("📥 Входные данные пользователя:\n");
promptBuilder.append(inputDataBuilder.toString());
promptBuilder.append("Если данных недостаточно — логично дополни, отметив: «Данные уточнены системой автоматически».\n\n");
promptBuilder.append("📊 Требования к числовым данным\n");
promptBuilder.append("Ты обязан сгенерировать реалистичные числовые значения, подходящие для автоматической визуализации.\n\n");
promptBuilder.append("1️⃣ Сезонность спроса (линейный график, 12 месяцев)\n");
promptBuilder.append("Выведи таблицу:\n");
promptBuilder.append("| Месяц | Уровень спроса (0–100) |\n");
promptBuilder.append("Значения должны:\n");
promptBuilder.append("- соответствовать нише,\n");
promptBuilder.append("- иметь сезонную динамику,\n");
promptBuilder.append("- быть реалистичными.\n\n");
promptBuilder.append("2️⃣ Сравнительный анализ конкурентов (столбчатая диаграмма)\n");
promptBuilder.append("Выведи таблицу:\n");
promptBuilder.append("| Конкурент | Охват аудитории | Активность в соцсетях (0–100) | Количество отзывов | Сильные стороны | Слабые стороны |\n");
promptBuilder.append("Охват — в диапазоне 1 000–50 000, Активность — 0–100.\n\n");
promptBuilder.append("3️⃣ Сегментация целевой аудитории (круговая диаграмма)\n");
promptBuilder.append("Определи:\n");
promptBuilder.append("- возрастные группы (%)\n");
promptBuilder.append("- пол (%)\n");
promptBuilder.append("- 3–5 ключевых сегментов ЦА (%)\n");
promptBuilder.append("Сумма процентов = 100%.\n\n");
promptBuilder.append("4️⃣ Потенциал каналов продвижения (бар-чарт)\n");
promptBuilder.append("Каналы: Instagram, TikTok, Telegram, 2GIS, Google Ads\n");
promptBuilder.append("Выведи таблицу:\n");
promptBuilder.append("| Канал | Потенциал (0–100) | Обоснование |\n\n");
promptBuilder.append("5️⃣ Конверсионная воронка (воронка спроса)\n");
promptBuilder.append("Выведи таблицу:\n");
promptBuilder.append("| Этап | Значение | Конверсия (%) |\n");
promptBuilder.append("Этапы:\n");
promptBuilder.append("- Охват\n");
promptBuilder.append("- Вовлечённость\n");
promptBuilder.append("- Клики\n");
promptBuilder.append("- Переходы\n");
promptBuilder.append("- Заявки\n");
promptBuilder.append("- Покупки\n");
promptBuilder.append("Числа должны уменьшаться логично.\n\n");
promptBuilder.append("📘 Теперь сформируй полный маркетинговый анализ:\n\n");
promptBuilder.append("I. Анализ продукта\n");
promptBuilder.append("- ключевые свойства\n");
promptBuilder.append("- преимущества\n");
promptBuilder.append("- возможные слабости\n");
promptBuilder.append("- ценность для рынка Казахстана\n\n");
promptBuilder.append("II. Анализ рынка Казахстана\n");
promptBuilder.append("- динамика\n");
promptBuilder.append("- тренды\n");
promptBuilder.append("- сезонность (используй данные из таблицы)\n");
promptBuilder.append("- возможности и ограничения\n\n");
promptBuilder.append("III. Анализ целевой аудитории\n");
promptBuilder.append("- портрет покупателя\n");
promptBuilder.append("- боли, потребности, мотивации\n");
promptBuilder.append("- триггеры принятия решения\n");
promptBuilder.append("- сегментация (вставить таблицу)\n\n");
promptBuilder.append("IV. Анализ конкурентов\n");
promptBuilder.append("Вставить таблицу конкурентов. Сформировать вывод о конкурентной среде. Указать рыночные ниши, которые свободны.\n\n");
promptBuilder.append("V. SWOT-анализ\n");
promptBuilder.append("Сформируй матрицу 2×2:\n");
promptBuilder.append("- Strengths\n");
promptBuilder.append("- Weaknesses\n");
promptBuilder.append("- Opportunities\n");
promptBuilder.append("- Threats\n\n");
promptBuilder.append("VI. Уникальное торговое предложение (УТП)\n");
promptBuilder.append("Создай 1–2 варианта УТП. Требования:\n");
promptBuilder.append("- ясно,\n");
promptBuilder.append("- коротко,\n");
promptBuilder.append("- конкретно,\n");
promptBuilder.append("- опираясь на анализ продукта и конкурентов.\n\n");
promptBuilder.append("VII. Рекомендации по позиционированию\n");
promptBuilder.append("Определи:\n");
promptBuilder.append("- образ бренда\n");
promptBuilder.append("- ключевое сообщение\n");
promptBuilder.append("- стиль коммуникации\n");
promptBuilder.append("- что важно подчеркнуть\n\n");
promptBuilder.append("VIII. Рекомендации по каналам продвижения\n");
promptBuilder.append("Вставить таблицу потенциала каналов. Дать объяснение, почему каждый канал подходит для этой ниши.\n\n");
promptBuilder.append("IX. Рекомендации по контенту\n");
promptBuilder.append("Приведи набор контент-направлений:\n");
promptBuilder.append("- продающий\n");
promptBuilder.append("- экспертный\n");
promptBuilder.append("- визуальный\n");
promptBuilder.append("- доверительный\n");
promptBuilder.append("- развлекательный\n");
promptBuilder.append("Дай примеры публикаций.\n\n");
promptBuilder.append("X. Итоговая стратегия продвижения\n");
promptBuilder.append("Сформулируй чётко:\n");
promptBuilder.append("- цель\n");
promptBuilder.append("- ключевые шаги\n");
promptBuilder.append("- что делать в первую очередь\n");
promptBuilder.append("- что даст максимальный эффект\n");
promptBuilder.append("- ожидаемый результат\n\n");
promptBuilder.append(lengthInstruction).append(" ");
promptBuilder.append("Ответ должен быть на русском языке.\n\n");
@@ -573,13 +1148,132 @@ public class MarketingAnalysisService {
}
markdown.append("\n");
// Display unified analysis
// 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 table
@SuppressWarnings("unchecked")
Map<String, Integer> seasonality = (Map<String, Integer>) chartsData.get("seasonality");
if (seasonality != null && !seasonality.isEmpty()) {
markdown.append("### 1️⃣ Сезонность спроса (12 месяцев)\n\n");
markdown.append("| Месяц | Уровень спроса (0–100) |\n");
markdown.append("|-------|------------------------|\n");
for (Map.Entry<String, Integer> entry : seasonality.entrySet()) {
markdown.append("| ").append(entry.getKey()).append(" | ").append(entry.getValue()).append(" |\n");
}
markdown.append("\n");
}
// Competitors table
@SuppressWarnings("unchecked")
List<Map<String, Object>> competitors = (List<Map<String, Object>>) chartsData.get("competitors");
if (competitors != null && !competitors.isEmpty()) {
markdown.append("### 2️⃣ Сравнительный анализ конкурентов\n\n");
markdown.append("| Конкурент | Охват аудитории | Активность в соцсетях (0–100) | Количество отзывов | Сильные стороны | Слабые стороны |\n");
markdown.append("|-----------|-----------------|-------------------------------|-------------------|----------------|----------------|\n");
for (Map<String, Object> comp : competitors) {
markdown.append("| ").append(comp.get("name")).append(" | ")
.append(comp.get("reach")).append(" | ")
.append(comp.get("activity")).append(" | ")
.append(comp.get("reviews")).append(" | ")
.append(comp.get("strengths")).append(" | ")
.append(comp.get("weaknesses")).append(" |\n");
}
markdown.append("\n");
}
// Audience segmentation tables
@SuppressWarnings("unchecked")
Map<String, Object> audienceSegmentation = (Map<String, Object>) chartsData.get("audienceSegmentation");
if (audienceSegmentation != null && !audienceSegmentation.isEmpty()) {
markdown.append("### 3️⃣ Сегментация целевой аудитории\n\n");
@SuppressWarnings("unchecked")
List<Map<String, String>> ageGroups = (List<Map<String, String>>) audienceSegmentation.get("ageGroups");
if (ageGroups != null && !ageGroups.isEmpty()) {
markdown.append("#### Возрастные группы\n\n");
markdown.append("| Возрастная группа | Процент (%) |\n");
markdown.append("|-------------------|-------------|\n");
for (Map<String, String> group : ageGroups) {
markdown.append("| ").append(group.get("label")).append(" | ").append(group.get("value")).append(" |\n");
}
markdown.append("\n");
}
@SuppressWarnings("unchecked")
List<Map<String, String>> genders = (List<Map<String, String>>) audienceSegmentation.get("genders");
if (genders != null && !genders.isEmpty()) {
markdown.append("#### Пол\n\n");
markdown.append("| Пол | Процент (%) |\n");
markdown.append("|-----|-------------|\n");
for (Map<String, String> gender : genders) {
markdown.append("| ").append(gender.get("label")).append(" | ").append(gender.get("value")).append(" |\n");
}
markdown.append("\n");
}
@SuppressWarnings("unchecked")
List<Map<String, String>> segments = (List<Map<String, String>>) audienceSegmentation.get("segments");
if (segments != null && !segments.isEmpty()) {
markdown.append("#### Сегменты ЦА\n\n");
markdown.append("| Сегмент | Процент (%) |\n");
markdown.append("|---------|-------------|\n");
for (Map<String, String> segment : segments) {
markdown.append("| ").append(segment.get("label")).append(" | ").append(segment.get("value")).append(" |\n");
}
markdown.append("\n");
}
}
// Channels potential table
@SuppressWarnings("unchecked")
List<Map<String, Object>> channelsPotential = (List<Map<String, Object>>) chartsData.get("channelsPotential");
if (channelsPotential != null && !channelsPotential.isEmpty()) {
markdown.append("### 4️⃣ Потенциал каналов продвижения\n\n");
markdown.append("| Канал | Потенциал (0–100) | Обоснование |\n");
markdown.append("|-------|-------------------|-------------|\n");
for (Map<String, Object> channel : channelsPotential) {
markdown.append("| ").append(channel.get("name")).append(" | ")
.append(channel.get("potential")).append(" | ")
.append(channel.get("justification")).append(" |\n");
}
markdown.append("\n");
}
// Conversion funnel table
@SuppressWarnings("unchecked")
List<Map<String, Object>> conversionFunnel = (List<Map<String, Object>>) chartsData.get("conversionFunnel");
if (conversionFunnel != null && !conversionFunnel.isEmpty()) {
markdown.append("### 5️⃣ Конверсионная воронка\n\n");
markdown.append("| Этап | Значение | Конверсия (%) |\n");
markdown.append("|------|----------|---------------|\n");
for (Map<String, Object> stage : conversionFunnel) {
markdown.append("| ").append(stage.get("stage")).append(" | ")
.append(stage.get("value")).append(" | ")
.append(stage.get("conversion")).append(" |\n");
}
markdown.append("\n");
}
}
// Display full analysis with 10 sections
String fullAnalysis = (String) reportData.get("fullAnalysis");
if (fullAnalysis != null && !fullAnalysis.trim().isEmpty()) {
markdown.append("## 📘 Полный маркетинговый анализ\n\n");
markdown.append(fullAnalysis).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("## Комплексный маркетинговый анализ\n\n");
markdown.append(summary).append("\n\n");
}
}
// Keep backward compatibility sections
@SuppressWarnings("unchecked")
Map<String, Object> targetAudience = (Map<String, Object>) reportData.get("targetAudience");
if (targetAudience != null) {
@@ -677,7 +1371,21 @@ public class MarketingAnalysisService {
MarketingAnalysis analysis) {
MarketingAnalysisResult.MarketingReport report = new MarketingAnalysisResult.MarketingReport();
// Set summary (backward compatibility)
report.setSummary((String) reportData.get("summary"));
// Set full analysis (new field)
String fullAnalysis = (String) reportData.get("fullAnalysis");
if (fullAnalysis != null) {
report.setFullAnalysis(fullAnalysis);
}
// Set charts data (new field)
@SuppressWarnings("unchecked")
Map<String, Object> chartsData = (Map<String, Object>) reportData.get("chartsData");
if (chartsData != null) {
report.setChartsData(chartsData);
}
@SuppressWarnings("unchecked")
Map<String, Object> targetAudienceData = (Map<String, Object>) reportData.get("targetAudience");