diff --git a/src/main/java/kz/konturai/parser/dto/MarketingAnalysisResponseV2.java b/src/main/java/kz/konturai/parser/dto/MarketingAnalysisResponseV2.java index e43dec0..d879483 100644 --- a/src/main/java/kz/konturai/parser/dto/MarketingAnalysisResponseV2.java +++ b/src/main/java/kz/konturai/parser/dto/MarketingAnalysisResponseV2.java @@ -1,5 +1,6 @@ package kz.konturai.parser.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.AllArgsConstructor; diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index ca9613f..9ddbe1a 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -635,40 +635,54 @@ public class MarketingAnalysisService { } public MarketingAnalysisResponseV2 generateAnalysisV2(MarketingAnalysisRequest request) { - logger.info(">>> Starting Marketing Analysis V2 for Niche: '{}', Region: '{}'", - request.getBusinessNiche(), request.getRegion()); + logger.info(">>> Starting Analysis V2 for Niche: {}", request.getBusinessNiche()); try { - // 1. Сбор данных (оставляем как есть, это работает нормально) + // 1. Сбор данных Map researchPack = buildResearchPack(request); + List competitors = resolveCompetitorCompaniesForBenchmarks(request, researchPack, 5); - // Получаем список конкурентов для контекста - List competitorCompanies = resolveCompetitorCompaniesForBenchmarks(request, researchPack, 5); + // 2. Генерируем ШАБЛОН СТРУКТУРЫ (пустой, с плейсхолдерами) + MarketingAnalysisResponseV2 structureTemplate = buildAnalysisV2ExampleForPrompt(request, competitors); + String jsonTemplate = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(structureTemplate); - // 2. Формирование Промпта - // ВАЖНО: Мы больше не передаем заполненный пример (exampleDto). - // Мы передаем только структуру (Schema) и жесткие инструкции. + // 3. Формируем инструкцию пользователю (User Prompt) + String userInstruction = """ + Твоя задача — сгенерировать маркетинговый анализ для: "%s" (Регион: %s). + + НИЖЕ ПРИВЕДЕН ШАБЛОН JSON. ТЫ ДОЛЖЕН ВЕРНУТЬ JSON ТОЧНО ТАКОЙ ЖЕ СТРУКТУРЫ. + ЗАМЕНИ ВСЕ ЗАГЛУШКИ (слова в [[...]] и нули) НА РЕАЛЬНЫЕ ДАННЫЕ ДЛЯ ЭТОЙ НИШИ. + + !!! ВАЖНОЕ ПРАВИЛО ПРО FinancialPerformance !!! + В объекте 'financial_performance' есть вложенные объекты (marketing_expenses, clients и т.д.). + У них есть только ДВА поля: "month" и "year". + ТЫ ДОЛЖЕН ЗАПИСАТЬ ТУДА ЗНАЧЕНИЯ (строки). + Пример правильного заполнения: "month": "1.5 млн KZT", "year": "18 млн KZT". + ЗАПРЕЩЕНО добавлять поле "value" или любые другие поля! + + ШАБЛОН ДЛЯ ЗАПОЛНЕНИЯ: + %s + """.formatted( + request.getBusinessNiche(), + request.getRegion(), + jsonTemplate + ); - String systemPrompt = buildSystemPromptForKazakhstan(); - String instruction = buildDetailedInstruction(request, competitorCompanies); - - // Собираем контекст (запрос + найденные данные) String contextJson = objectMapper.writeValueAsString(Map.of( - "user_input", request, - "search_data", researchPack, // Данные из Google/SerpApi - "identified_competitors", competitorCompanies + "user_request", request, + "search_results", researchPack, + "competitors_list", competitors )); - // 3. Вызов OpenAI + // 4. Вызов AI (с защитой от сбоев) for (int attempt = 1; attempt <= 2; attempt++) { try { - // Используем модель с большим контекстом (gpt-4o или gpt-4-turbo) String aiResponse = openAIAnalyticsService.generateWithInstructionWithModel( contextJson, - instruction, + userInstruction, "ru", - "gpt-4o", // Обязательно мощная модель для аналитики - systemPrompt, + "gpt-4o", + buildSystemPromptForKazakhstan(), 16000, 180000L ); @@ -677,23 +691,28 @@ public class MarketingAnalysisService { throw new IllegalStateException("OpenAI returned empty response"); } + // Чистка String cleaned = cleanJsonFromMarkdown(aiResponse); - MarketingAnalysisResponseV2 parsed = objectMapper.readValue(cleaned, MarketingAnalysisResponseV2.class); - // Финальная валидация (если вдруг вернул фитнес данные - можно тут добавить проверку) - return parsed; + // ЭКСТРЕННАЯ ЗАЩИТА: Если он все-таки сгенерил "value", вырезаем его на лету + if (cleaned.contains("\"value\"")) { + logger.warn("AI hallucinated 'value' field. Attempting to fix string."); + cleaned = cleaned.replaceAll("\"value\"\\s*:", "\"ignore_this_field\":"); + } + + return objectMapper.readValue(cleaned, MarketingAnalysisResponseV2.class); } catch (Exception e) { - logger.error("JSON Parsing/Generation failed on attempt {}: {}", attempt, e.getMessage()); + logger.error("Attempt {} failed: {}", attempt, e.getMessage()); if (attempt == 2) throw e; - Thread.sleep(2000); + Thread.sleep(1500); } } - throw new IllegalStateException("Failed to generate analysis."); + throw new IllegalStateException("Failed to generate analysis"); } catch (Exception e) { - logger.error("Fatal error: {}", e.getMessage(), e); - throw new RuntimeException(e); + logger.error("Analysis generation fatal error: {}", e.getMessage(), e); + throw new RuntimeException("Ошибка анализа: " + e.getMessage(), e); } } @@ -1426,8 +1445,7 @@ public class MarketingAnalysisService { private MarketingAnalysisResponseV2 buildAnalysisV2ExampleForPrompt(MarketingAnalysisRequest request, List competitorCompanies) { MarketingAnalysisResponseV2 response = new MarketingAnalysisResponseV2(); - // Используем нишу из запроса для заголовка примера, чтобы ГПТ понимал контекст - String nicheTitle = (request.getBusinessNiche() != null) ? request.getBusinessNiche() : "Фитнес услуги"; + String nicheTitle = (request.getBusinessNiche() != null) ? request.getBusinessNiche() : "[[НИША]]"; response.setReportTitle("Стратегический анализ рынка: " + nicheTitle); MarketingAnalysisResponseV2.Sections sections = new MarketingAnalysisResponseV2.Sections(); @@ -1437,36 +1455,27 @@ public class MarketingAnalysisService { marketOverview.setTitle("Обзор рынка"); MarketingAnalysisResponseV2.MarketOverviewMetrics moMetrics = new MarketingAnalysisResponseV2.MarketOverviewMetrics(); - moMetrics.setOnlineDemandIndex("Высокий (8/10)"); - moMetrics.setOnlinePotential("Растущий тренд )"); - moMetrics.setDemandGrowthRateYoy("+18.5%"); - moMetrics.setSeasonalityCoefficientSpring("1.25 (Высокий сезон)"); - moMetrics.setSeasonalityRange("Сентябрь - Май"); + // ЗАМЕНЕНО НА ИНСТРУКЦИИ + moMetrics.setOnlineDemandIndex("[[ОЦЕНИТЬ_СПРОС_1_10]]"); + moMetrics.setOnlinePotential("[[ТРЕНД_РЫНКА_ТЕКСТОМ]]"); + moMetrics.setDemandGrowthRateYoy("[[ПРОЦЕНТ_РОСТА_ДЛЯ_ЭТОЙ_НИШИ]]"); + moMetrics.setSeasonalityCoefficientSpring("[[КОЭФФИЦИЕНТ_СЕЗОННОСТИ]]"); + moMetrics.setSeasonalityRange("[[МЕСЯЦЫ_ПИКА_ПРОДАЖ]]"); marketOverview.setMetrics(moMetrics); MarketingAnalysisResponseV2.MonthlyOnlineIndex monthlyOnlineIndex = new MarketingAnalysisResponseV2.MonthlyOnlineIndex(); - // Реалистичная сезонность (спад летом, рост к весне) - monthlyOnlineIndex.setJanuary(0.95); monthlyOnlineIndex.setFebruary(0.90); monthlyOnlineIndex.setMarch(1.0); - monthlyOnlineIndex.setApril(0.95); monthlyOnlineIndex.setMay(0.85); monthlyOnlineIndex.setJune(0.70); - monthlyOnlineIndex.setJuly(0.60); monthlyOnlineIndex.setAugust(0.75); monthlyOnlineIndex.setSeptember(0.90); - monthlyOnlineIndex.setOctober(0.95); monthlyOnlineIndex.setNovember(1.0); monthlyOnlineIndex.setDecember(0.98); + // Ставим 1.0, чтобы показать, что это Double, но не навязывать тренд фитнеса + monthlyOnlineIndex.setJanuary(1.0); monthlyOnlineIndex.setFebruary(1.0); monthlyOnlineIndex.setMarch(1.0); + monthlyOnlineIndex.setApril(1.0); monthlyOnlineIndex.setMay(1.0); monthlyOnlineIndex.setJune(1.0); + monthlyOnlineIndex.setJuly(1.0); monthlyOnlineIndex.setAugust(1.0); monthlyOnlineIndex.setSeptember(1.0); + monthlyOnlineIndex.setOctober(1.0); monthlyOnlineIndex.setNovember(1.0); monthlyOnlineIndex.setDecember(1.0); marketOverview.setMonthlyOnlineIndex(monthlyOnlineIndex); - // Логика городов: берем из запроса или дефолт - Map geography = new LinkedHashMap<>(); - List reqRegions = request.getRegion(); - if (reqRegions != null && !reqRegions.isEmpty()) { - int step = 100 / Math.max(1, reqRegions.size()); - for (int i = 0; i < reqRegions.size(); i++) { - geography.put(reqRegions.get(i), Math.max(10, 90 - (i * 15))); // Разные значения - } - } else { - geography.put("Алматы", 50); - geography.put("Астана", 35); - geography.put("Шымкент", 15); - } + Map geography = new java.util.LinkedHashMap<>(); + geography.put("Город 1", 100); + geography.put("Город 2", 80); marketOverview.setGeography(geography); - marketOverview.setConclusion("Рынок находится в стадии активного роста. Основной спрос сконцентрирован в мегаполисах, сезонность ярко выражена."); + marketOverview.setConclusion("[[АНАЛИТИЧЕСКИЙ_ВЫВОД_ПО_НИШЕ]]"); sections.setMarketOverview(marketOverview); // 2. Target Audience @@ -1474,64 +1483,53 @@ public class MarketingAnalysisService { targetAudience.setTitle("Портрет целевой аудитории"); MarketingAnalysisResponseV2.TargetAudienceMetrics taMetrics = new MarketingAnalysisResponseV2.TargetAudienceMetrics(); - taMetrics.setTotalAudienceSize("~450 000 активных пользователей"); - taMetrics.setLeadingSegmentShare("Женщины 25-34 года"); - taMetrics.setAboveAverageIncomeShare("42% (Средний+)"); + taMetrics.setTotalAudienceSize("[[КОЛИЧЕСТВО_ПОТЕНЦИАЛЬНЫХ_КЛИЕНТОВ]]"); + taMetrics.setLeadingSegmentShare("[[ОСНОВНОЙ_СЕГМЕНТ_ПОЛ_ВОЗРАСТ]]"); + taMetrics.setAboveAverageIncomeShare("[[ПРОЦЕНТ_ПЛАТЕЖЕСПОСОБНЫХ]]"); targetAudience.setMetrics(taMetrics); - targetAudience.setSegments(List.of( - new MarketingAnalysisResponseV2.AudienceSegment("Ядро (Core)", "45% - Покупают регулярно"), - new MarketingAnalysisResponseV2.AudienceSegment("Потенциальные", "30% - Сравнивают цены"), - new MarketingAnalysisResponseV2.AudienceSegment("Ситуативные", "25% - Ищут акции") + targetAudience.setSegments(java.util.List.of( + new MarketingAnalysisResponseV2.AudienceSegment("[[НАЗВАНИЕ_СЕГМЕНТА_1]]", "[[ОПИСАНИЕ_И_ДОЛЯ]]"), + new MarketingAnalysisResponseV2.AudienceSegment("[[НАЗВАНИЕ_СЕГМЕНТА_2]]", "[[ОПИСАНИЕ_И_ДОЛЯ]]") )); - Map ageStructure = new LinkedHashMap<>(); - ageStructure.put("18-24", "15%"); ageStructure.put("25-34", "45%"); - ageStructure.put("35-44", "25%"); ageStructure.put("45+", "15%"); + java.util.Map ageStructure = new java.util.LinkedHashMap<>(); + ageStructure.put("18-24", "[[%]]"); ageStructure.put("25-34", "[[%]]"); + ageStructure.put("35-44", "[[%]]"); ageStructure.put("45+", "[[%]]"); targetAudience.setAgeStructure(ageStructure); - Map incomeByAge = new LinkedHashMap<>(); - incomeByAge.put("18-24", 180000); incomeByAge.put("25-34", 380000); - incomeByAge.put("35-44", 450000); incomeByAge.put("45+", 320000); + java.util.Map incomeByAge = new java.util.LinkedHashMap<>(); + // Ставим 0, чтобы ИИ понял, что нужны числа (Integer), а не строки + incomeByAge.put("18-24", 0); incomeByAge.put("25-34", 0); + incomeByAge.put("35-44", 0); incomeByAge.put("45+", 0); targetAudience.setIncomeByAge(incomeByAge); - targetAudience.setConclusion("Ключевой клиент — специалист 25-35 лет с доходом выше среднего. Вторичный сегмент — студенты (чувствительны к цене)."); + targetAudience.setConclusion("[[ВЫВОД_О_КЛИЕНТАХ]]"); sections.setTargetAudience(targetAudience); - + // 3. Competitive Environment MarketingAnalysisResponseV2.CompetitiveEnvironment competitiveEnvironment = new MarketingAnalysisResponseV2.CompetitiveEnvironment(); competitiveEnvironment.setTitle("Конкурентный ландшафт"); - // 3.1 Новые метрики MarketingAnalysisResponseV2.CompetitiveEnvironmentMetrics ceMetrics = new MarketingAnalysisResponseV2.CompetitiveEnvironmentMetrics(); - ceMetrics.setLeaderMarketShare("45-50%"); - ceMetrics.setCompetitionLevel("Высокая (Красный океан)"); - ceMetrics.setAdPressure("Агрессивная (Высокие ставки)"); - ceMetrics.setMarketLeaderName("Сетевые лидеры / Маркетплейсы"); + ceMetrics.setLeaderMarketShare("[[ДОЛЯ_ЛИДЕРА]]"); + ceMetrics.setCompetitionLevel("[[УРОВЕНЬ_КОНКУРЕНЦИИ]]"); + ceMetrics.setAdPressure("[[ДАВЛЕНИЕ_В_РЕКЛАМЕ]]"); + ceMetrics.setMarketLeaderName("[[ТИП_ИЛИ_ИМЯ_ЛИДЕРА]]"); competitiveEnvironment.setMetrics(ceMetrics); - // 3.2 Список конкурентов (List) - для таблицы и графика - List competitorRows = new java.util.ArrayList<>(); + java.util.List competitorRows = new java.util.ArrayList<>(); + // Здесь мы оставляем список пустым или с заглушкой, чтобы ИИ заполнил сам на основе списка competitorCompanies + MarketingAnalysisResponseV2.CompetitorRow row = new MarketingAnalysisResponseV2.CompetitorRow(); + row.setName("[[ИМЯ_КОНКУРЕНТА_1]]"); + row.setWebsite("[[САЙТ_ИЛИ_ЗАГЛУШКА]]"); + row.setVisibilityScore(100); + row.setKeyStrengths(java.util.List.of("[[СИЛЬНАЯ_СТОРОНА]]")); + row.setPriceSegment("[[СЕГМЕНТ]]"); + competitorRows.add(row); - // Берем реальных конкурентов из поиска или дефолтные, если список пуст - List activeCompetitors = (competitorCompanies != null && !competitorCompanies.isEmpty()) - ? competitorCompanies - : List.of("Конкурент A (Лидер)", "Конкурент B (Сетевой)", "Конкурент C (Дискаунтер)"); - - for (int i = 0; i < Math.min(activeCompetitors.size(), 5); i++) { - MarketingAnalysisResponseV2.CompetitorRow row = new MarketingAnalysisResponseV2.CompetitorRow(); - row.setName(activeCompetitors.get(i)); - // Генерируем ссылку-заглушку, которую GPT должен заменить на реальную - row.setWebsite("https://google.com/search?q=" + activeCompetitors.get(i).replace(" ", "+")); - row.setVisibilityScore(90 - (i * 12)); // 90, 78, 66... (Красивый график) - row.setKeyStrengths(List.of("Сильный бренд", "Широкая сеть филиалов", "Активный SMM")); - row.setPriceSegment(i == 0 ? "Premium" : (i == 1 ? "Low-cost" : "Middle")); - - competitorRows.add(row); - } competitiveEnvironment.setCompetitors(competitorRows); - - competitiveEnvironment.setConclusion("Рынок консолидирован крупными игроками. Для успешного входа требуется четкое УТП или узкое нишевание."); + competitiveEnvironment.setConclusion("[[ВЫВОД_О_КОНКУРЕНТАХ]]"); sections.setCompetitiveEnvironment(competitiveEnvironment); // 4. Demand And Behavior @@ -1539,33 +1537,33 @@ public class MarketingAnalysisService { demandAndBehavior.setTitle("Спрос и поведение"); MarketingAnalysisResponseV2.DemandAndBehaviorMetrics dabMetrics = new MarketingAnalysisResponseV2.DemandAndBehaviorMetrics(); - dabMetrics.setSearchVolumeGrowth("+22% YoY"); - dabMetrics.setInteractionsBeforePurchase("5-7 касаний"); - dabMetrics.setRepeatRefusalProbability("~15%"); + dabMetrics.setSearchVolumeGrowth("[[РОСТ_ПОИСКА_YOY]]"); + dabMetrics.setInteractionsBeforePurchase("[[КОЛ-ВО_КАСАНИЙ]]"); + dabMetrics.setRepeatRefusalProbability("[[ВЕРОЯТНОСТЬ_ОТКАЗА]]"); demandAndBehavior.setMetrics(dabMetrics); - Map searchQueryTypes = new LinkedHashMap<>(); - searchQueryTypes.put("direct_interest_catalog", "40% (Прямой спрос)"); - searchQueryTypes.put("question_based_articles", "25% (Как выбрать?)"); - searchQueryTypes.put("competitors_reviews", "20% (Сравнение)"); - searchQueryTypes.put("problem_solving_hot_promo", "15% (Акции)"); + java.util.Map searchQueryTypes = new java.util.LinkedHashMap<>(); + searchQueryTypes.put("direct_interest_catalog", "[[% (Прямой спрос)]]"); + searchQueryTypes.put("question_based_articles", "[[% (Инфо запросы)]]"); + searchQueryTypes.put("competitors_reviews", "[[% (Сравнение)]]"); + searchQueryTypes.put("problem_solving_hot_promo", "[[% (Акции)]]"); demandAndBehavior.setSearchQueryTypes(searchQueryTypes); - Map decisionFactors = new LinkedHashMap<>(); - decisionFactors.put("promo_discount", "35% (Цена)"); - decisionFactors.put("review_recommendation", "25% (Отзывы)"); - decisionFactors.put("warranty_quality", "20% (Качество)"); - decisionFactors.put("convenience_service", "15% (Сервис)"); - decisionFactors.put("availability", "5% (Наличие)"); - decisionFactors.put("price", "Важно"); // Дублирует promo, но нужно для структуры + java.util.Map decisionFactors = new java.util.LinkedHashMap<>(); + decisionFactors.put("promo_discount", "[[% (Цена)]]"); + decisionFactors.put("review_recommendation", "[[% (Отзывы)]]"); + decisionFactors.put("warranty_quality", "[[% (Качество)]]"); + decisionFactors.put("convenience_service", "[[% (Сервис)]]"); + decisionFactors.put("availability", "[[% (Наличие)]]"); + decisionFactors.put("price", "Важно"); demandAndBehavior.setDecisionFactors(decisionFactors); - Map reasonsForRefusal = new LinkedHashMap<>(); - reasonsForRefusal.put("high_price", "40% (Дорого)"); - reasonsForRefusal.put("no_reviews_social_proof", "30% (Нет доверия)"); - reasonsForRefusal.put("choice_difficulty_unclear_offer", "30% (Непонятно)"); + java.util.Map reasonsForRefusal = new java.util.LinkedHashMap<>(); + reasonsForRefusal.put("high_price", "[[% (Причина 1)]]"); + reasonsForRefusal.put("no_reviews_social_proof", "[[% (Причина 2)]]"); + reasonsForRefusal.put("choice_difficulty_unclear_offer", "[[% (Причина 3)]]"); demandAndBehavior.setReasonsForRefusal(reasonsForRefusal); - demandAndBehavior.setConclusion("Покупатели чувствительны к цене и отзывам. Важно наличие социального доказательства."); + demandAndBehavior.setConclusion("[[ВЫВОД_О_ПОВЕДЕНИИ]]"); sections.setDemandAndBehavior(demandAndBehavior); // 5. Customer Journey @@ -1573,25 +1571,24 @@ public class MarketingAnalysisService { customerJourney.setTitle("Путь клиента"); MarketingAnalysisResponseV2.FunnelMetrics funnelMetrics = new MarketingAnalysisResponseV2.FunnelMetrics(); - funnelMetrics.setAdTraffic("15 000"); - funnelMetrics.setSiteVisitors("12 000 (CTR 80%)"); // Условный переход - funnelMetrics.setLeads(450); // 3-4% конверсия - funnelMetrics.setBookings(200); // Квалифицированные - funnelMetrics.setPayments(120); // Продажи + funnelMetrics.setAdTraffic("[[ТРАФИК]]"); + funnelMetrics.setSiteVisitors("[[ПОСЕТИТЕЛИ]]"); + funnelMetrics.setLeads(0); + funnelMetrics.setBookings(0); + funnelMetrics.setPayments(0); customerJourney.setFunnelMetrics(funnelMetrics); MarketingAnalysisResponseV2.ConversionRates conversionRates = new MarketingAnalysisResponseV2.ConversionRates(); - conversionRates.setLeadToBooking("45%"); - conversionRates.setBookingToPayment("60%"); - conversionRates.setLeadToPaymentTotal("27%"); + conversionRates.setLeadToBooking("[[%]]"); + conversionRates.setBookingToPayment("[[%]]"); + conversionRates.setLeadToPaymentTotal("[[%]]"); customerJourney.setConversionRates(conversionRates); MarketingAnalysisResponseV2.FunnelBottlenecks funnelBottlenecks = new MarketingAnalysisResponseV2.FunnelBottlenecks(); - funnelBottlenecks.setLeadToBooking("Долгая обработка заявки / Недозвон"); - funnelBottlenecks.setBookingToPayment("Высокая цена / Нет рассрочки"); + funnelBottlenecks.setLeadToBooking("[[ПРОБЛЕМА_ЭТАПА_1]]"); + funnelBottlenecks.setBookingToPayment("[[ПРОБЛЕМА_ЭТАПА_2]]"); customerJourney.setFunnelBottlenecks(funnelBottlenecks); - - customerJourney.setConclusion("Основные потери лидов происходят на этапе первого звонка и квалификации."); + customerJourney.setConclusion("[[ВЫВОД_ПО_ВОРОНКЕ]]"); sections.setCustomerJourney(customerJourney); // 6. Acquisition Channels @@ -1599,82 +1596,74 @@ public class MarketingAnalysisService { acquisitionChannels.setTitle("Каналы привлечения"); MarketingAnalysisResponseV2.AcquisitionChannelsMetrics acMetrics = new MarketingAnalysisResponseV2.AcquisitionChannelsMetrics(); - acMetrics.setSearchTrafficShare("35%"); - acMetrics.setAverageLeadCost("2500 - 4000 KZT"); - acMetrics.setGrowthPotential("TikTok / Reels"); + acMetrics.setSearchTrafficShare("[[%]]"); + acMetrics.setAverageLeadCost("[[CPL_В_KZT]]"); + acMetrics.setGrowthPotential("[[КАНАЛ_РОСТА]]"); acquisitionChannels.setMetrics(acMetrics); - Map channelShares = new LinkedHashMap<>(); - channelShares.put("instagram_target", "45%"); - channelShares.put("tiktok_ads", "25%"); - channelShares.put("kaspi_marketing", "15%"); - channelShares.put("google_ads", "10%"); - channelShares.put("2gis_ads", "5%"); + java.util.Map channelShares = new java.util.LinkedHashMap<>(); + channelShares.put("instagram_target", "[[%]]"); + channelShares.put("tiktok_ads", "[[%]]"); + channelShares.put("kaspi_marketing", "[[%]]"); + channelShares.put("google_ads", "[[%]]"); acquisitionChannels.setChannelShares(channelShares); - Map channelEfficiency = new LinkedHashMap<>(); - channelEfficiency.put("instagram", new MarketingAnalysisResponseV2.ChannelEfficiency("2500 KZT", "High")); - channelEfficiency.put("tiktok", new MarketingAnalysisResponseV2.ChannelEfficiency("1800 KZT", "High")); - channelEfficiency.put("kaspi", new MarketingAnalysisResponseV2.ChannelEfficiency("2000 KZT", "Very High")); - channelEfficiency.put("google_search", new MarketingAnalysisResponseV2.ChannelEfficiency("4500 KZT", "Medium")); - channelEfficiency.put("2gis", new MarketingAnalysisResponseV2.ChannelEfficiency("Бесплатно", "Local")); - channelEfficiency.put("threads_meta", new MarketingAnalysisResponseV2.ChannelEfficiency("N/A", "New")); + java.util.Map channelEfficiency = new java.util.LinkedHashMap<>(); + channelEfficiency.put("instagram", new MarketingAnalysisResponseV2.ChannelEfficiency("[[СТОИМОСТЬ]]", "High")); + channelEfficiency.put("tiktok", new MarketingAnalysisResponseV2.ChannelEfficiency("[[СТОИМОСТЬ]]", "High")); acquisitionChannels.setChannelEfficiency(channelEfficiency); - - acquisitionChannels.setConclusion("Instagram и TikTok — наиболее рентабельные каналы. Kaspi обязателен для товарного бизнеса."); + acquisitionChannels.setConclusion("[[ВЫВОД_ПО_КАНАЛАМ]]"); sections.setAcquisitionChannels(acquisitionChannels); - // 7. Marketing Economics + // 7. Marketing Economics (ВОТ ТУТ МЫ УБИРАЕМ ВЫДУМАННЫЕ ЦИФРЫ) MarketingAnalysisResponseV2.MarketingEconomics marketingEconomics = new MarketingAnalysisResponseV2.MarketingEconomics(); marketingEconomics.setTitle("Экономика маркетинга"); MarketingAnalysisResponseV2.MarketingEconomicsMetrics meMetrics = new MarketingAnalysisResponseV2.MarketingEconomicsMetrics(); - meMetrics.setCustomerAcquisitionCostCac("3 500 KZT"); - meMetrics.setRepeatSalesConversion("30%"); - meMetrics.setRoi("250%"); + meMetrics.setCustomerAcquisitionCostCac("[[CAC_В_KZT]]"); + meMetrics.setRepeatSalesConversion("[[RETENTION_%]]"); + meMetrics.setRoi("[[ROI_%]]"); marketingEconomics.setMetrics(meMetrics); MarketingAnalysisResponseV2.FinancialPerformance financialPerformance = new MarketingAnalysisResponseV2.FinancialPerformance(); - // Используем бюджет ~1.5 млн - financialPerformance.setMarketingExpenses(new MarketingAnalysisResponseV2.MonthYearValue("1.5 млн KZT", "18 млн KZT")); - financialPerformance.setClients(new MarketingAnalysisResponseV2.MonthYearValue("~430", "~5 160")); - financialPerformance.setCac(new MarketingAnalysisResponseV2.MonthYearValue("3 500 KZT", "3 200 KZT")); - financialPerformance.setRevenueFromClient(new MarketingAnalysisResponseV2.MonthYearValue("20 000 KZT", "240 000 KZT")); - financialPerformance.setProfitFromClient(new MarketingAnalysisResponseV2.MonthYearValue("8 000 KZT", "96 000 KZT")); + // ВАЖНО: Мы показываем, что month и year - это String поля для значений + financialPerformance.setMarketingExpenses(new MarketingAnalysisResponseV2.MonthYearValue("[[РАСХОД_МЕСЯЦ]]", "[[РАСХОД_ГОД]]")); + financialPerformance.setClients(new MarketingAnalysisResponseV2.MonthYearValue("[[КЛИЕНТОВ_МЕСЯЦ]]", "[[КЛИЕНТОВ_ГОД]]")); + financialPerformance.setCac(new MarketingAnalysisResponseV2.MonthYearValue("[[CAC_МЕСЯЦ]]", "[[CAC_ГОД]]")); + financialPerformance.setRevenueFromClient(new MarketingAnalysisResponseV2.MonthYearValue("[[ВЫРУЧКА_МЕСЯЦ]]", "[[ВЫРУЧКА_ГОД]]")); + financialPerformance.setProfitFromClient(new MarketingAnalysisResponseV2.MonthYearValue("[[ПРИБЫЛЬ_МЕСЯЦ]]", "[[ПРИБЫЛЬ_ГОД]]")); marketingEconomics.setFinancialPerformance(financialPerformance); - marketingEconomics.setConclusion("Экономика сходится с положительным ROI. Рекомендуется реинвестировать прибыль в масштабирование."); + marketingEconomics.setConclusion("[[ФИНАНСОВЫЙ_ВЫВОД]]"); sections.setMarketingEconomics(marketingEconomics); // 8. Bottlenecks MarketingAnalysisResponseV2.Bottlenecks bottlenecks = new MarketingAnalysisResponseV2.Bottlenecks(); bottlenecks.setTitle("Узкие места"); - Map statistics = new LinkedHashMap<>(); - statistics.put("landing_page_issues", "25% (Отказы)"); - statistics.put("slow_manager_responses", "30% (Потери)"); - statistics.put("repeat_interaction_refusal", "20% (Недожим)"); + java.util.Map statistics = new java.util.LinkedHashMap<>(); + statistics.put("landing_page_issues", "[[%]]"); + statistics.put("slow_manager_responses", "[[%]]"); bottlenecks.setStatistics(statistics); - MarketingAnalysisResponseV2.BottlenecksDetails details = new MarketingAnalysisResponseV2.BottlenecksDetails(); - details.setLanding(List.of("Медленная загрузка", "Непонятное УТП", "Нет мобильной адаптации")); - details.setLeadManagement(List.of("Долгий ответ (>15 мин)", "Нет скриптов", "Нет CRM")); - details.setRetention(List.of("Нет программы лояльности", "Редкие касания базы", "Высокая цена без ценности")); - bottlenecks.setDetails(details); - - bottlenecks.setConclusion("Технические проблемы сайта и отсутствие CRM — главные барьеры."); + MarketingAnalysisResponseV2.BottlenecksDetails bDetails = new MarketingAnalysisResponseV2.BottlenecksDetails(); + bDetails.setLanding(java.util.List.of("[[ПРОБЛЕМА_1]]", "[[ПРОБЛЕМА_2]]")); + bDetails.setLeadManagement(java.util.List.of("[[ПРОБЛЕМА_1]]")); + bDetails.setRetention(java.util.List.of("[[ПРОБЛЕМА_1]]")); + bottlenecks.setDetails(bDetails); + bottlenecks.setConclusion("[[ВЫВОД_О_ПРОБЛЕМАХ]]"); sections.setBottlenecks(bottlenecks); // 9. Recommendations MarketingAnalysisResponseV2.Recommendations recommendations = new MarketingAnalysisResponseV2.Recommendations(); recommendations.setTitle("Рекомендации"); - recommendations.setCorePillars(List.of( - new MarketingAnalysisResponseV2.CorePillar("Быстрый старт", "Запустить таргет с лид-магнитом."), - new MarketingAnalysisResponseV2.CorePillar("Оптимизация", "Внедрить CRM и скрипты продаж."), - new MarketingAnalysisResponseV2.CorePillar("Масштабирование", "Подключить TikTok Ads и блогеров."), - new MarketingAnalysisResponseV2.CorePillar("Удержание", "Внедрить систему лояльности.") + recommendations.setCorePillars(java.util.List.of( + new MarketingAnalysisResponseV2.CorePillar("Быстрый старт", "[[ДЕЙСТВИЕ_1]]"), + new MarketingAnalysisResponseV2.CorePillar("Оптимизация", "[[ДЕЙСТВИЕ_2]]"), + new MarketingAnalysisResponseV2.CorePillar("Масштабирование", "[[ДЕЙСТВИЕ_3]]"), + new MarketingAnalysisResponseV2.CorePillar("Удержание", "[[ДЕЙСТВИЕ_4]]") )); - recommendations.setConclusion("Реализация плана позволит удвоить количество лидов за 3 месяца."); + recommendations.setConclusion("[[ФИНАЛЬНЫЙ_СОВЕТ]]"); sections.setRecommendations(recommendations); response.setSections(sections);