diff --git a/src/main/java/kz/konturai/parser/dto/MarketingAnalysisResult.java b/src/main/java/kz/konturai/parser/dto/MarketingAnalysisResult.java index a242c56..cbfd30e 100644 --- a/src/main/java/kz/konturai/parser/dto/MarketingAnalysisResult.java +++ b/src/main/java/kz/konturai/parser/dto/MarketingAnalysisResult.java @@ -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 chartsData; private TargetAudience targetAudience; private List 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 getChartsData() { + return chartsData; + } + + public void setChartsData(Map chartsData) { + this.chartsData = chartsData; + } + public TargetAudience getTargetAudience() { return targetAudience; } diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index 860bd33..f1f08ff 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -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 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 analysisTypes, DetailLevel detailLevel) { - if (analysisTypes == null || analysisTypes.isEmpty()) { - analysisTypes = List.of(AnalysisType.РЫНОК); // Default + private Map generateChartData(String context, MarketingAnalysisRequest request) { + Map 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 parsedData = parseChartDataFromResponse(chartDataResponse); + chartsData.putAll(parsedData); } + + return chartsData; + } + + private Map parseChartDataFromResponse(String response) { + Map chartData = new HashMap<>(); + + // Parse seasonality data + Map seasonality = parseSeasonalityData(response); + if (!seasonality.isEmpty()) { + chartData.put("seasonality", seasonality); + } + + // Parse competitors data + List> competitors = parseCompetitorsData(response); + if (!competitors.isEmpty()) { + chartData.put("competitors", competitors); + } + + // Parse audience segmentation + Map audienceSegmentation = parseAudienceSegmentation(response); + if (!audienceSegmentation.isEmpty()) { + chartData.put("audienceSegmentation", audienceSegmentation); + } + + // Parse channels potential + List> channelsPotential = parseChannelsPotential(response); + if (!channelsPotential.isEmpty()) { + chartData.put("channelsPotential", channelsPotential); + } + + // Parse conversion funnel + List> conversionFunnel = parseConversionFunnel(response); + if (!conversionFunnel.isEmpty()) { + chartData.put("conversionFunnel", conversionFunnel); + } + + return chartData; + } + + private Map parseSeasonalityData(String response) { + Map 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> parseCompetitorsData(String response) { + List> 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 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 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 parseAudienceSegmentation(String response) { + Map segmentation = new HashMap<>(); + List> ageGroups = new ArrayList<>(); + List> genders = new ArrayList<>(); + List> 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 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> 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> 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> parseChannelsPotential(String response) { + List> 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 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 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> parseConversionFunnel(String response) { + List> 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 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 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 targetAudienceData = request.getTargetAudience(); + if (targetAudienceData != null) { + StringBuilder audienceBuilder = new StringBuilder(); + if (targetAudienceData.containsKey("genders")) { + @SuppressWarnings("unchecked") + List genders = (List) targetAudienceData.get("genders"); + if (genders != null && !genders.isEmpty()) { + audienceBuilder.append("Гендер: ").append(String.join(", ", genders)).append(". "); + } } - sectionNumber++; + if (targetAudienceData.containsKey("ageRanges")) { + @SuppressWarnings("unchecked") + List ageRanges = (List) targetAudienceData.get("ageRanges"); + if (ageRanges != null && !ageRanges.isEmpty()) { + audienceBuilder.append("Возраст: ").append(String.join(", ", ageRanges)).append(". "); + } + } + if (targetAudienceData.containsKey("types")) { + @SuppressWarnings("unchecked") + List types = (List) 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 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 chartsData = (Map) reportData.get("chartsData"); + if (chartsData != null && !chartsData.isEmpty()) { + markdown.append("## 📊 Числовые данные для графиков\n\n"); + + // Seasonality table + @SuppressWarnings("unchecked") + Map seasonality = (Map) 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 entry : seasonality.entrySet()) { + markdown.append("| ").append(entry.getKey()).append(" | ").append(entry.getValue()).append(" |\n"); + } + markdown.append("\n"); + } + + // Competitors table + @SuppressWarnings("unchecked") + List> competitors = (List>) chartsData.get("competitors"); + if (competitors != null && !competitors.isEmpty()) { + markdown.append("### 2️⃣ Сравнительный анализ конкурентов\n\n"); + markdown.append("| Конкурент | Охват аудитории | Активность в соцсетях (0–100) | Количество отзывов | Сильные стороны | Слабые стороны |\n"); + markdown.append("|-----------|-----------------|-------------------------------|-------------------|----------------|----------------|\n"); + for (Map 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 audienceSegmentation = (Map) chartsData.get("audienceSegmentation"); + if (audienceSegmentation != null && !audienceSegmentation.isEmpty()) { + markdown.append("### 3️⃣ Сегментация целевой аудитории\n\n"); + + @SuppressWarnings("unchecked") + List> ageGroups = (List>) audienceSegmentation.get("ageGroups"); + if (ageGroups != null && !ageGroups.isEmpty()) { + markdown.append("#### Возрастные группы\n\n"); + markdown.append("| Возрастная группа | Процент (%) |\n"); + markdown.append("|-------------------|-------------|\n"); + for (Map group : ageGroups) { + markdown.append("| ").append(group.get("label")).append(" | ").append(group.get("value")).append(" |\n"); + } + markdown.append("\n"); + } + + @SuppressWarnings("unchecked") + List> genders = (List>) audienceSegmentation.get("genders"); + if (genders != null && !genders.isEmpty()) { + markdown.append("#### Пол\n\n"); + markdown.append("| Пол | Процент (%) |\n"); + markdown.append("|-----|-------------|\n"); + for (Map gender : genders) { + markdown.append("| ").append(gender.get("label")).append(" | ").append(gender.get("value")).append(" |\n"); + } + markdown.append("\n"); + } + + @SuppressWarnings("unchecked") + List> segments = (List>) audienceSegmentation.get("segments"); + if (segments != null && !segments.isEmpty()) { + markdown.append("#### Сегменты ЦА\n\n"); + markdown.append("| Сегмент | Процент (%) |\n"); + markdown.append("|---------|-------------|\n"); + for (Map segment : segments) { + markdown.append("| ").append(segment.get("label")).append(" | ").append(segment.get("value")).append(" |\n"); + } + markdown.append("\n"); + } + } + + // Channels potential table + @SuppressWarnings("unchecked") + List> channelsPotential = (List>) chartsData.get("channelsPotential"); + if (channelsPotential != null && !channelsPotential.isEmpty()) { + markdown.append("### 4️⃣ Потенциал каналов продвижения\n\n"); + markdown.append("| Канал | Потенциал (0–100) | Обоснование |\n"); + markdown.append("|-------|-------------------|-------------|\n"); + for (Map 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> conversionFunnel = (List>) chartsData.get("conversionFunnel"); + if (conversionFunnel != null && !conversionFunnel.isEmpty()) { + markdown.append("### 5️⃣ Конверсионная воронка\n\n"); + markdown.append("| Этап | Значение | Конверсия (%) |\n"); + markdown.append("|------|----------|---------------|\n"); + for (Map 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 targetAudience = (Map) 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 chartsData = (Map) reportData.get("chartsData"); + if (chartsData != null) { + report.setChartsData(chartsData); + } @SuppressWarnings("unchecked") Map targetAudienceData = (Map) reportData.get("targetAudience"); diff --git a/ФИНАЛЬНЫЙ ПРОМТ ДЛЯ ГЕНЕРАЦИИ МАРКЕТИНГОВОГО АНАЛИЗА С ГРАФИКАМИ И ЧИСЛОВЫМИ ПОКАЗАТЕЛЯМИ.md b/ФИНАЛЬНЫЙ ПРОМТ ДЛЯ ГЕНЕРАЦИИ МАРКЕТИНГОВОГО АНАЛИЗА С ГРАФИКАМИ И ЧИСЛОВЫМИ ПОКАЗАТЕЛЯМИ.md new file mode 100644 index 0000000..50ae9c5 --- /dev/null +++ b/ФИНАЛЬНЫЙ ПРОМТ ДЛЯ ГЕНЕРАЦИИ МАРКЕТИНГОВОГО АНАЛИЗА С ГРАФИКАМИ И ЧИСЛОВЫМИ ПОКАЗАТЕЛЯМИ.md @@ -0,0 +1,116 @@ +ФИНАЛЬНЫЙ ПРОМТ ДЛЯ ГЕНЕРАЦИИ МАРКЕТИНГОВОГО АНАЛИЗА С ГРАФИКАМИ И ЧИСЛОВЫМИ ПОКАЗАТЕЛЯМИ +(готов к использованию в backend или системе агентов) + +Ты — профессиональный маркетолог и аналитик, работающий с малым и средним бизнесом Казахстана.
Твоя задача — выполнить глубокий маркетинговый анализ компании пользователя с генерацией всех необходимых количественных данных для построения графиков и диаграмм.
Пиши структурированно, лаконично, без воды, понятным языком, ориентируясь на предпринимателя МСБ. + +📥 Входные данные пользователя: +Название компании/продукта: {{company_name}} +Описание продукта: {{product_description}} +Целевая аудитория: {{target_audience}} +Конкуренты: {{competitors}} +Цель продвижения: {{goals}} +Регион/город: {{region}} +Если данных недостаточно — логично дополни, отметив:
«Данные уточнены системой автоматически». + +📊 Требования к числовым данным +Ты обязан сгенерировать реалистичные числовые значения, подходящие для автоматической визуализации. + +1️⃣ Сезонность спроса (линейный график, 12 месяцев) +Выведи таблицу: +| Месяц | Уровень спроса (0–100) | +Значения должны: +соответствовать нише, +иметь сезонную динамику, +быть реалистичными. + +2️⃣ Сравнительный анализ конкурентов (столбчатая диаграмма) +Выведи таблицу: +| Конкурент | Охват аудитории | Активность в соцсетях (0–100) | Количество отзывов | Сильные стороны | Слабые стороны | +Охват — в диапазоне 1 000–50 000,
Активность — 0–100. + +3️⃣ Сегментация целевой аудитории (круговая диаграмма) +Определи: +возрастные группы (%) +пол (%) +3–5 ключевых сегментов ЦА (%) +Сумма процентов = 100%. + +4️⃣ Потенциал каналов продвижения (бар-чарт) +Каналы: Instagram, TikTok, Telegram, 2GIS, Google Ads
Выведи таблицу: +| Канал | Потенциал (0–100) | Обоснование | + +5️⃣ Конверсионная воронка (воронка спроса) +Выведи таблицу: +| Этап | Значение | Конверсия (%) | +Этапы: +Охват +Вовлечённость +Клики +Переходы +Заявки +Покупки +Числа должны уменьшаться логично. + +📘 Теперь сформируй полный маркетинговый анализ: + +I. Анализ продукта +ключевые свойства +преимущества +возможные слабости +ценность для рынка Казахстана + +II. Анализ рынка Казахстана +динамика +тренды +сезонность (используй данные из таблицы) +возможности и ограничения + +III. Анализ целевой аудитории +портрет покупателя +боли, потребности, мотивации +триггеры принятия решения +сегментация (вставить таблицу) + +IV. Анализ конкурентов +Вставить таблицу конкурентов.
Сформировать вывод о конкурентной среде.
Указать рыночные ниши, которые свободны. + +V. SWOT-анализ +Сформируй матрицу 2×2: +Strengths +Weaknesses +Opportunities +Threats + +VI. Уникальное торговое предложение (УТП) +Создай 1–2 варианта УТП.
Требования: +ясно, +коротко, +конкретно, +опираясь на анализ продукта и конкурентов. + +VII. Рекомендации по позиционированию +Определи: +образ бренда +ключевое сообщение +стиль коммуникации +что важно подчеркнуть + +VIII. Рекомендации по каналам продвижения +Вставить таблицу потенциала каналов.
Дать объяснение, почему каждый канал подходит для этой ниши. + +IX. Рекомендации по контенту +Приведи набор контент-направлений: +продающий +экспертный +визуальный +доверительный +развлекательный +Дай примеры публикаций. + +X. Итоговая стратегия продвижения +Сформулируй чётко: +цель +ключевые шаги +что делать в первую очередь +что даст максимальный эффект +ожидаемый результат