This commit is contained in:
arys
2026-01-26 21:46:54 +05:00
parent c8088caa1f
commit d766c10449
2 changed files with 162 additions and 172 deletions
@@ -1,5 +1,6 @@
package kz.konturai.parser.dto; package kz.konturai.parser.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming; import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -635,40 +635,54 @@ public class MarketingAnalysisService {
} }
public MarketingAnalysisResponseV2 generateAnalysisV2(MarketingAnalysisRequest request) { public MarketingAnalysisResponseV2 generateAnalysisV2(MarketingAnalysisRequest request) {
logger.info(">>> Starting Marketing Analysis V2 for Niche: '{}', Region: '{}'", logger.info(">>> Starting Analysis V2 for Niche: {}", request.getBusinessNiche());
request.getBusinessNiche(), request.getRegion());
try { try {
// 1. Сбор данных (оставляем как есть, это работает нормально) // 1. Сбор данных
Map<String, Object> researchPack = buildResearchPack(request); Map<String, Object> researchPack = buildResearchPack(request);
List<String> competitors = resolveCompetitorCompaniesForBenchmarks(request, researchPack, 5);
// Получаем список конкурентов для контекста // 2. Генерируем ШАБЛОН СТРУКТУРЫ (пустой, с плейсхолдерами)
List<String> competitorCompanies = resolveCompetitorCompaniesForBenchmarks(request, researchPack, 5); MarketingAnalysisResponseV2 structureTemplate = buildAnalysisV2ExampleForPrompt(request, competitors);
String jsonTemplate = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(structureTemplate);
// 2. Формирование Промпта // 3. Формируем инструкцию пользователю (User Prompt)
// ВАЖНО: Мы больше не передаем заполненный пример (exampleDto). String userInstruction = """
// Мы передаем только структуру (Schema) и жесткие инструкции. Твоя задача сгенерировать маркетинговый анализ для: "%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( String contextJson = objectMapper.writeValueAsString(Map.of(
"user_input", request, "user_request", request,
"search_data", researchPack, // Данные из Google/SerpApi "search_results", researchPack,
"identified_competitors", competitorCompanies "competitors_list", competitors
)); ));
// 3. Вызов OpenAI // 4. Вызов AI (с защитой от сбоев)
for (int attempt = 1; attempt <= 2; attempt++) { for (int attempt = 1; attempt <= 2; attempt++) {
try { try {
// Используем модель с большим контекстом (gpt-4o или gpt-4-turbo)
String aiResponse = openAIAnalyticsService.generateWithInstructionWithModel( String aiResponse = openAIAnalyticsService.generateWithInstructionWithModel(
contextJson, contextJson,
instruction, userInstruction,
"ru", "ru",
"gpt-4o", // Обязательно мощная модель для аналитики "gpt-4o",
systemPrompt, buildSystemPromptForKazakhstan(),
16000, 16000,
180000L 180000L
); );
@@ -677,23 +691,28 @@ public class MarketingAnalysisService {
throw new IllegalStateException("OpenAI returned empty response"); throw new IllegalStateException("OpenAI returned empty response");
} }
// Чистка
String cleaned = cleanJsonFromMarkdown(aiResponse); String cleaned = cleanJsonFromMarkdown(aiResponse);
MarketingAnalysisResponseV2 parsed = objectMapper.readValue(cleaned, MarketingAnalysisResponseV2.class);
// Финальная валидация (если вдруг вернул фитнес данные - можно тут добавить проверку) // ЭКСТРЕННАЯ ЗАЩИТА: Если он все-таки сгенерил "value", вырезаем его на лету
return parsed; 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) { } 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; 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) { } catch (Exception e) {
logger.error("Fatal error: {}", e.getMessage(), e); logger.error("Analysis generation fatal error: {}", e.getMessage(), e);
throw new RuntimeException(e); throw new RuntimeException("Ошибка анализа: " + e.getMessage(), e);
} }
} }
@@ -1426,8 +1445,7 @@ public class MarketingAnalysisService {
private MarketingAnalysisResponseV2 buildAnalysisV2ExampleForPrompt(MarketingAnalysisRequest request, private MarketingAnalysisResponseV2 buildAnalysisV2ExampleForPrompt(MarketingAnalysisRequest request,
List<String> competitorCompanies) { List<String> competitorCompanies) {
MarketingAnalysisResponseV2 response = new MarketingAnalysisResponseV2(); MarketingAnalysisResponseV2 response = new MarketingAnalysisResponseV2();
// Используем нишу из запроса для заголовка примера, чтобы ГПТ понимал контекст String nicheTitle = (request.getBusinessNiche() != null) ? request.getBusinessNiche() : "[[НИША]]";
String nicheTitle = (request.getBusinessNiche() != null) ? request.getBusinessNiche() : "Фитнес услуги";
response.setReportTitle("Стратегический анализ рынка: " + nicheTitle); response.setReportTitle("Стратегический анализ рынка: " + nicheTitle);
MarketingAnalysisResponseV2.Sections sections = new MarketingAnalysisResponseV2.Sections(); MarketingAnalysisResponseV2.Sections sections = new MarketingAnalysisResponseV2.Sections();
@@ -1437,36 +1455,27 @@ public class MarketingAnalysisService {
marketOverview.setTitle("Обзор рынка"); marketOverview.setTitle("Обзор рынка");
MarketingAnalysisResponseV2.MarketOverviewMetrics moMetrics = new MarketingAnalysisResponseV2.MarketOverviewMetrics(); MarketingAnalysisResponseV2.MarketOverviewMetrics moMetrics = new MarketingAnalysisResponseV2.MarketOverviewMetrics();
moMetrics.setOnlineDemandIndex("Высокий (8/10)"); // ЗАМЕНЕНО НА ИНСТРУКЦИИ
moMetrics.setOnlinePotential("Растущий тренд )"); moMetrics.setOnlineDemandIndex("[[ОЦЕНИТЬ_СПРОС_1_10]]");
moMetrics.setDemandGrowthRateYoy("+18.5%"); moMetrics.setOnlinePotential("[[ТРЕНД_РЫНКА_ТЕКСТОМ]]");
moMetrics.setSeasonalityCoefficientSpring("1.25 (Высокий сезон)"); moMetrics.setDemandGrowthRateYoy("[[ПРОЦЕНТ_РОСТА_ДЛЯ_ЭТОЙ_НИШИ]]");
moMetrics.setSeasonalityRange("Сентябрь - Май"); moMetrics.setSeasonalityCoefficientSpring("[[КОЭФФИЦИЕНТ_СЕЗОННОСТИ]]");
moMetrics.setSeasonalityRange("[[МЕСЯЦЫ_ПИКА_ПРОДАЖ]]");
marketOverview.setMetrics(moMetrics); marketOverview.setMetrics(moMetrics);
MarketingAnalysisResponseV2.MonthlyOnlineIndex monthlyOnlineIndex = new MarketingAnalysisResponseV2.MonthlyOnlineIndex(); MarketingAnalysisResponseV2.MonthlyOnlineIndex monthlyOnlineIndex = new MarketingAnalysisResponseV2.MonthlyOnlineIndex();
// Реалистичная сезонность (спад летом, рост к весне) // Ставим 1.0, чтобы показать, что это Double, но не навязывать тренд фитнеса
monthlyOnlineIndex.setJanuary(0.95); monthlyOnlineIndex.setFebruary(0.90); monthlyOnlineIndex.setMarch(1.0); monthlyOnlineIndex.setJanuary(1.0); monthlyOnlineIndex.setFebruary(1.0); monthlyOnlineIndex.setMarch(1.0);
monthlyOnlineIndex.setApril(0.95); monthlyOnlineIndex.setMay(0.85); monthlyOnlineIndex.setJune(0.70); monthlyOnlineIndex.setApril(1.0); monthlyOnlineIndex.setMay(1.0); monthlyOnlineIndex.setJune(1.0);
monthlyOnlineIndex.setJuly(0.60); monthlyOnlineIndex.setAugust(0.75); monthlyOnlineIndex.setSeptember(0.90); monthlyOnlineIndex.setJuly(1.0); monthlyOnlineIndex.setAugust(1.0); monthlyOnlineIndex.setSeptember(1.0);
monthlyOnlineIndex.setOctober(0.95); monthlyOnlineIndex.setNovember(1.0); monthlyOnlineIndex.setDecember(0.98); monthlyOnlineIndex.setOctober(1.0); monthlyOnlineIndex.setNovember(1.0); monthlyOnlineIndex.setDecember(1.0);
marketOverview.setMonthlyOnlineIndex(monthlyOnlineIndex); marketOverview.setMonthlyOnlineIndex(monthlyOnlineIndex);
// Логика городов: берем из запроса или дефолт Map<String, Integer> geography = new java.util.LinkedHashMap<>();
Map<String, Integer> geography = new LinkedHashMap<>(); geography.put("Город 1", 100);
List<String> reqRegions = request.getRegion(); geography.put("Город 2", 80);
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);
}
marketOverview.setGeography(geography); marketOverview.setGeography(geography);
marketOverview.setConclusion("Рынок находится в стадии активного роста. Основной спрос сконцентрирован в мегаполисах, сезонность ярко выражена."); marketOverview.setConclusion("[[АНАЛИТИЧЕСКИЙ_ВЫВОД_ПО_НИШЕ]]");
sections.setMarketOverview(marketOverview); sections.setMarketOverview(marketOverview);
// 2. Target Audience // 2. Target Audience
@@ -1474,64 +1483,53 @@ public class MarketingAnalysisService {
targetAudience.setTitle("Портрет целевой аудитории"); targetAudience.setTitle("Портрет целевой аудитории");
MarketingAnalysisResponseV2.TargetAudienceMetrics taMetrics = new MarketingAnalysisResponseV2.TargetAudienceMetrics(); MarketingAnalysisResponseV2.TargetAudienceMetrics taMetrics = new MarketingAnalysisResponseV2.TargetAudienceMetrics();
taMetrics.setTotalAudienceSize("~450 000 активных пользователей"); taMetrics.setTotalAudienceSize("[[КОЛИЧЕСТВО_ПОТЕНЦИАЛЬНЫХ_КЛИЕНТОВ]]");
taMetrics.setLeadingSegmentShare("Женщины 25-34 года"); taMetrics.setLeadingSegmentShare("[[ОСНОВНОЙ_СЕГМЕНТ_ПОЛ_ВОЗРАСТ]]");
taMetrics.setAboveAverageIncomeShare("42% (Средний+)"); taMetrics.setAboveAverageIncomeShare("[[ПРОЦЕНТ_ПЛАТЕЖЕСПОСОБНЫХ]]");
targetAudience.setMetrics(taMetrics); targetAudience.setMetrics(taMetrics);
targetAudience.setSegments(List.of( targetAudience.setSegments(java.util.List.of(
new MarketingAnalysisResponseV2.AudienceSegment("Ядро (Core)", "45% - Покупают регулярно"), new MarketingAnalysisResponseV2.AudienceSegment("[[НАЗВАНИЕ_СЕГМЕНТА_1]]", "[[ОПИСАНИЕ_И_ДОЛЯ]]"),
new MarketingAnalysisResponseV2.AudienceSegment("Потенциальные", "30% - Сравнивают цены"), new MarketingAnalysisResponseV2.AudienceSegment("[[НАЗВАНИЕ_СЕГМЕНТА_2]]", "[[ОПИСАНИЕ_И_ДОЛЯ]]")
new MarketingAnalysisResponseV2.AudienceSegment("Ситуативные", "25% - Ищут акции")
)); ));
Map<String, String> ageStructure = new LinkedHashMap<>(); java.util.Map<String, String> ageStructure = new java.util.LinkedHashMap<>();
ageStructure.put("18-24", "15%"); ageStructure.put("25-34", "45%"); ageStructure.put("18-24", "[[%]]"); ageStructure.put("25-34", "[[%]]");
ageStructure.put("35-44", "25%"); ageStructure.put("45+", "15%"); ageStructure.put("35-44", "[[%]]"); ageStructure.put("45+", "[[%]]");
targetAudience.setAgeStructure(ageStructure); targetAudience.setAgeStructure(ageStructure);
Map<String, Integer> incomeByAge = new LinkedHashMap<>(); java.util.Map<String, Integer> incomeByAge = new java.util.LinkedHashMap<>();
incomeByAge.put("18-24", 180000); incomeByAge.put("25-34", 380000); // Ставим 0, чтобы ИИ понял, что нужны числа (Integer), а не строки
incomeByAge.put("35-44", 450000); incomeByAge.put("45+", 320000); incomeByAge.put("18-24", 0); incomeByAge.put("25-34", 0);
incomeByAge.put("35-44", 0); incomeByAge.put("45+", 0);
targetAudience.setIncomeByAge(incomeByAge); targetAudience.setIncomeByAge(incomeByAge);
targetAudience.setConclusion("Ключевой клиент — специалист 25-35 лет с доходом выше среднего. Вторичный сегмент — студенты (чувствительны к цене)."); targetAudience.setConclusion("[[ВЫВОД_О_КЛИЕНТАХ]]");
sections.setTargetAudience(targetAudience); sections.setTargetAudience(targetAudience);
// 3. Competitive Environment
MarketingAnalysisResponseV2.CompetitiveEnvironment competitiveEnvironment = new MarketingAnalysisResponseV2.CompetitiveEnvironment(); MarketingAnalysisResponseV2.CompetitiveEnvironment competitiveEnvironment = new MarketingAnalysisResponseV2.CompetitiveEnvironment();
competitiveEnvironment.setTitle("Конкурентный ландшафт"); competitiveEnvironment.setTitle("Конкурентный ландшафт");
// 3.1 Новые метрики
MarketingAnalysisResponseV2.CompetitiveEnvironmentMetrics ceMetrics = new MarketingAnalysisResponseV2.CompetitiveEnvironmentMetrics(); MarketingAnalysisResponseV2.CompetitiveEnvironmentMetrics ceMetrics = new MarketingAnalysisResponseV2.CompetitiveEnvironmentMetrics();
ceMetrics.setLeaderMarketShare("45-50%"); ceMetrics.setLeaderMarketShare("[[ДОЛЯ_ЛИДЕРА]]");
ceMetrics.setCompetitionLevel("Высокая (Красный океан)"); ceMetrics.setCompetitionLevel("[[УРОВЕНЬ_КОНКУРЕНЦИИ]]");
ceMetrics.setAdPressure("Агрессивная (Высокие ставки)"); ceMetrics.setAdPressure("[[ДАВЛЕНИЕ_В_РЕКЛАМЕ]]");
ceMetrics.setMarketLeaderName("Сетевые лидеры / Маркетплейсы"); ceMetrics.setMarketLeaderName("[[ТИП_ИЛИ_ИМЯ_ЛИДЕРА]]");
competitiveEnvironment.setMetrics(ceMetrics); competitiveEnvironment.setMetrics(ceMetrics);
// 3.2 Список конкурентов (List<CompetitorRow>) - для таблицы и графика java.util.List<MarketingAnalysisResponseV2.CompetitorRow> competitorRows = new java.util.ArrayList<>();
List<MarketingAnalysisResponseV2.CompetitorRow> 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<String> 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.setCompetitors(competitorRows);
competitiveEnvironment.setConclusion("[[ВЫВОД_О_КОНКУРЕНТАХ]]");
competitiveEnvironment.setConclusion("Рынок консолидирован крупными игроками. Для успешного входа требуется четкое УТП или узкое нишевание.");
sections.setCompetitiveEnvironment(competitiveEnvironment); sections.setCompetitiveEnvironment(competitiveEnvironment);
// 4. Demand And Behavior // 4. Demand And Behavior
@@ -1539,33 +1537,33 @@ public class MarketingAnalysisService {
demandAndBehavior.setTitle("Спрос и поведение"); demandAndBehavior.setTitle("Спрос и поведение");
MarketingAnalysisResponseV2.DemandAndBehaviorMetrics dabMetrics = new MarketingAnalysisResponseV2.DemandAndBehaviorMetrics(); MarketingAnalysisResponseV2.DemandAndBehaviorMetrics dabMetrics = new MarketingAnalysisResponseV2.DemandAndBehaviorMetrics();
dabMetrics.setSearchVolumeGrowth("+22% YoY"); dabMetrics.setSearchVolumeGrowth("[[РОСТ_ПОИСКА_YOY]]");
dabMetrics.setInteractionsBeforePurchase("5-7 касаний"); dabMetrics.setInteractionsBeforePurchase("[[КОЛ-ВО_КАСАНИЙ]]");
dabMetrics.setRepeatRefusalProbability("~15%"); dabMetrics.setRepeatRefusalProbability("[[ВЕРОЯТНОСТЬ_ОТКАЗА]]");
demandAndBehavior.setMetrics(dabMetrics); demandAndBehavior.setMetrics(dabMetrics);
Map<String, String> searchQueryTypes = new LinkedHashMap<>(); java.util.Map<String, String> searchQueryTypes = new java.util.LinkedHashMap<>();
searchQueryTypes.put("direct_interest_catalog", "40% (Прямой спрос)"); searchQueryTypes.put("direct_interest_catalog", "[[% (Прямой спрос)]]");
searchQueryTypes.put("question_based_articles", "25% (Как выбрать?)"); searchQueryTypes.put("question_based_articles", "[[% (Инфо запросы)]]");
searchQueryTypes.put("competitors_reviews", "20% (Сравнение)"); searchQueryTypes.put("competitors_reviews", "[[% (Сравнение)]]");
searchQueryTypes.put("problem_solving_hot_promo", "15% (Акции)"); searchQueryTypes.put("problem_solving_hot_promo", "[[% (Акции)]]");
demandAndBehavior.setSearchQueryTypes(searchQueryTypes); demandAndBehavior.setSearchQueryTypes(searchQueryTypes);
Map<String, String> decisionFactors = new LinkedHashMap<>(); java.util.Map<String, String> decisionFactors = new java.util.LinkedHashMap<>();
decisionFactors.put("promo_discount", "35% (Цена)"); decisionFactors.put("promo_discount", "[[% (Цена)]]");
decisionFactors.put("review_recommendation", "25% (Отзывы)"); decisionFactors.put("review_recommendation", "[[% (Отзывы)]]");
decisionFactors.put("warranty_quality", "20% (Качество)"); decisionFactors.put("warranty_quality", "[[% (Качество)]]");
decisionFactors.put("convenience_service", "15% (Сервис)"); decisionFactors.put("convenience_service", "[[% (Сервис)]]");
decisionFactors.put("availability", "5% (Наличие)"); decisionFactors.put("availability", "[[% (Наличие)]]");
decisionFactors.put("price", "Важно"); // Дублирует promo, но нужно для структуры decisionFactors.put("price", "Важно");
demandAndBehavior.setDecisionFactors(decisionFactors); demandAndBehavior.setDecisionFactors(decisionFactors);
Map<String, String> reasonsForRefusal = new LinkedHashMap<>(); java.util.Map<String, String> reasonsForRefusal = new java.util.LinkedHashMap<>();
reasonsForRefusal.put("high_price", "40% (Дорого)"); reasonsForRefusal.put("high_price", "[[% (Причина 1)]]");
reasonsForRefusal.put("no_reviews_social_proof", "30% (Нет доверия)"); reasonsForRefusal.put("no_reviews_social_proof", "[[% (Причина 2)]]");
reasonsForRefusal.put("choice_difficulty_unclear_offer", "30% (Непонятно)"); reasonsForRefusal.put("choice_difficulty_unclear_offer", "[[% (Причина 3)]]");
demandAndBehavior.setReasonsForRefusal(reasonsForRefusal); demandAndBehavior.setReasonsForRefusal(reasonsForRefusal);
demandAndBehavior.setConclusion("Покупатели чувствительны к цене и отзывам. Важно наличие социального доказательства."); demandAndBehavior.setConclusion("[[ВЫВОД_О_ПОВЕДЕНИИ]]");
sections.setDemandAndBehavior(demandAndBehavior); sections.setDemandAndBehavior(demandAndBehavior);
// 5. Customer Journey // 5. Customer Journey
@@ -1573,25 +1571,24 @@ public class MarketingAnalysisService {
customerJourney.setTitle("Путь клиента"); customerJourney.setTitle("Путь клиента");
MarketingAnalysisResponseV2.FunnelMetrics funnelMetrics = new MarketingAnalysisResponseV2.FunnelMetrics(); MarketingAnalysisResponseV2.FunnelMetrics funnelMetrics = new MarketingAnalysisResponseV2.FunnelMetrics();
funnelMetrics.setAdTraffic("15 000"); funnelMetrics.setAdTraffic("[[ТРАФИК]]");
funnelMetrics.setSiteVisitors("12 000 (CTR 80%)"); // Условный переход funnelMetrics.setSiteVisitors("[[ПОСЕТИТЕЛИ]]");
funnelMetrics.setLeads(450); // 3-4% конверсия funnelMetrics.setLeads(0);
funnelMetrics.setBookings(200); // Квалифицированные funnelMetrics.setBookings(0);
funnelMetrics.setPayments(120); // Продажи funnelMetrics.setPayments(0);
customerJourney.setFunnelMetrics(funnelMetrics); customerJourney.setFunnelMetrics(funnelMetrics);
MarketingAnalysisResponseV2.ConversionRates conversionRates = new MarketingAnalysisResponseV2.ConversionRates(); MarketingAnalysisResponseV2.ConversionRates conversionRates = new MarketingAnalysisResponseV2.ConversionRates();
conversionRates.setLeadToBooking("45%"); conversionRates.setLeadToBooking("[[%]]");
conversionRates.setBookingToPayment("60%"); conversionRates.setBookingToPayment("[[%]]");
conversionRates.setLeadToPaymentTotal("27%"); conversionRates.setLeadToPaymentTotal("[[%]]");
customerJourney.setConversionRates(conversionRates); customerJourney.setConversionRates(conversionRates);
MarketingAnalysisResponseV2.FunnelBottlenecks funnelBottlenecks = new MarketingAnalysisResponseV2.FunnelBottlenecks(); MarketingAnalysisResponseV2.FunnelBottlenecks funnelBottlenecks = new MarketingAnalysisResponseV2.FunnelBottlenecks();
funnelBottlenecks.setLeadToBooking("Долгая обработка заявки / Недозвон"); funnelBottlenecks.setLeadToBooking("[[ПРОБЛЕМА_ЭТАПА_1]]");
funnelBottlenecks.setBookingToPayment("Высокая цена / Нет рассрочки"); funnelBottlenecks.setBookingToPayment("[[ПРОБЛЕМА_ЭТАПА_2]]");
customerJourney.setFunnelBottlenecks(funnelBottlenecks); customerJourney.setFunnelBottlenecks(funnelBottlenecks);
customerJourney.setConclusion("[[ВЫВОД_ПО_ВОРОНКЕ]]");
customerJourney.setConclusion("Основные потери лидов происходят на этапе первого звонка и квалификации.");
sections.setCustomerJourney(customerJourney); sections.setCustomerJourney(customerJourney);
// 6. Acquisition Channels // 6. Acquisition Channels
@@ -1599,82 +1596,74 @@ public class MarketingAnalysisService {
acquisitionChannels.setTitle("Каналы привлечения"); acquisitionChannels.setTitle("Каналы привлечения");
MarketingAnalysisResponseV2.AcquisitionChannelsMetrics acMetrics = new MarketingAnalysisResponseV2.AcquisitionChannelsMetrics(); MarketingAnalysisResponseV2.AcquisitionChannelsMetrics acMetrics = new MarketingAnalysisResponseV2.AcquisitionChannelsMetrics();
acMetrics.setSearchTrafficShare("35%"); acMetrics.setSearchTrafficShare("[[%]]");
acMetrics.setAverageLeadCost("2500 - 4000 KZT"); acMetrics.setAverageLeadCost("[[CPL_В_KZT]]");
acMetrics.setGrowthPotential("TikTok / Reels"); acMetrics.setGrowthPotential("[[КАНАЛ_РОСТА]]");
acquisitionChannels.setMetrics(acMetrics); acquisitionChannels.setMetrics(acMetrics);
Map<String, String> channelShares = new LinkedHashMap<>(); java.util.Map<String, String> channelShares = new java.util.LinkedHashMap<>();
channelShares.put("instagram_target", "45%"); channelShares.put("instagram_target", "[[%]]");
channelShares.put("tiktok_ads", "25%"); channelShares.put("tiktok_ads", "[[%]]");
channelShares.put("kaspi_marketing", "15%"); channelShares.put("kaspi_marketing", "[[%]]");
channelShares.put("google_ads", "10%"); channelShares.put("google_ads", "[[%]]");
channelShares.put("2gis_ads", "5%");
acquisitionChannels.setChannelShares(channelShares); acquisitionChannels.setChannelShares(channelShares);
Map<String, MarketingAnalysisResponseV2.ChannelEfficiency> channelEfficiency = new LinkedHashMap<>(); java.util.Map<String, MarketingAnalysisResponseV2.ChannelEfficiency> channelEfficiency = new java.util.LinkedHashMap<>();
channelEfficiency.put("instagram", new MarketingAnalysisResponseV2.ChannelEfficiency("2500 KZT", "High")); channelEfficiency.put("instagram", new MarketingAnalysisResponseV2.ChannelEfficiency("[[СТОИМОСТЬ]]", "High"));
channelEfficiency.put("tiktok", new MarketingAnalysisResponseV2.ChannelEfficiency("1800 KZT", "High")); channelEfficiency.put("tiktok", new MarketingAnalysisResponseV2.ChannelEfficiency("[[СТОИМОСТЬ]]", "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"));
acquisitionChannels.setChannelEfficiency(channelEfficiency); acquisitionChannels.setChannelEfficiency(channelEfficiency);
acquisitionChannels.setConclusion("[[ВЫВОД_ПО_КАНАЛАМ]]");
acquisitionChannels.setConclusion("Instagram и TikTok — наиболее рентабельные каналы. Kaspi обязателен для товарного бизнеса.");
sections.setAcquisitionChannels(acquisitionChannels); sections.setAcquisitionChannels(acquisitionChannels);
// 7. Marketing Economics // 7. Marketing Economics (ВОТ ТУТ МЫ УБИРАЕМ ВЫДУМАННЫЕ ЦИФРЫ)
MarketingAnalysisResponseV2.MarketingEconomics marketingEconomics = new MarketingAnalysisResponseV2.MarketingEconomics(); MarketingAnalysisResponseV2.MarketingEconomics marketingEconomics = new MarketingAnalysisResponseV2.MarketingEconomics();
marketingEconomics.setTitle("Экономика маркетинга"); marketingEconomics.setTitle("Экономика маркетинга");
MarketingAnalysisResponseV2.MarketingEconomicsMetrics meMetrics = new MarketingAnalysisResponseV2.MarketingEconomicsMetrics(); MarketingAnalysisResponseV2.MarketingEconomicsMetrics meMetrics = new MarketingAnalysisResponseV2.MarketingEconomicsMetrics();
meMetrics.setCustomerAcquisitionCostCac("3 500 KZT"); meMetrics.setCustomerAcquisitionCostCac("[[CAC_В_KZT]]");
meMetrics.setRepeatSalesConversion("30%"); meMetrics.setRepeatSalesConversion("[[RETENTION_%]]");
meMetrics.setRoi("250%"); meMetrics.setRoi("[[ROI_%]]");
marketingEconomics.setMetrics(meMetrics); marketingEconomics.setMetrics(meMetrics);
MarketingAnalysisResponseV2.FinancialPerformance financialPerformance = new MarketingAnalysisResponseV2.FinancialPerformance(); MarketingAnalysisResponseV2.FinancialPerformance financialPerformance = new MarketingAnalysisResponseV2.FinancialPerformance();
// Используем бюджет ~1.5 млн // ВАЖНО: Мы показываем, что month и year - это String поля для значений
financialPerformance.setMarketingExpenses(new MarketingAnalysisResponseV2.MonthYearValue("1.5 млн KZT", "18 млн KZT")); financialPerformance.setMarketingExpenses(new MarketingAnalysisResponseV2.MonthYearValue("[[РАСХОД_МЕСЯЦ]]", "[[РАСХОД_ГОД]]"));
financialPerformance.setClients(new MarketingAnalysisResponseV2.MonthYearValue("~430", "~5 160")); financialPerformance.setClients(new MarketingAnalysisResponseV2.MonthYearValue("[[КЛИЕНТОВ_МЕСЯЦ]]", "[[КЛИЕНТОВ_ГОД]]"));
financialPerformance.setCac(new MarketingAnalysisResponseV2.MonthYearValue("3 500 KZT", "3 200 KZT")); financialPerformance.setCac(new MarketingAnalysisResponseV2.MonthYearValue("[[CAC_МЕСЯЦ]]", "[[CAC_ГОД]]"));
financialPerformance.setRevenueFromClient(new MarketingAnalysisResponseV2.MonthYearValue("20 000 KZT", "240 000 KZT")); financialPerformance.setRevenueFromClient(new MarketingAnalysisResponseV2.MonthYearValue("[[ВЫРУЧКА_МЕСЯЦ]]", "[[ВЫРУЧКА_ГОД]]"));
financialPerformance.setProfitFromClient(new MarketingAnalysisResponseV2.MonthYearValue("8 000 KZT", "96 000 KZT")); financialPerformance.setProfitFromClient(new MarketingAnalysisResponseV2.MonthYearValue("[[ПРИБЫЛЬ_МЕСЯЦ]]", "[[ПРИБЫЛЬ_ГОД]]"));
marketingEconomics.setFinancialPerformance(financialPerformance); marketingEconomics.setFinancialPerformance(financialPerformance);
marketingEconomics.setConclusion("Экономика сходится с положительным ROI. Рекомендуется реинвестировать прибыль в масштабирование."); marketingEconomics.setConclusion("[[ФИНАНСОВЫЙ_ВЫВОД]]");
sections.setMarketingEconomics(marketingEconomics); sections.setMarketingEconomics(marketingEconomics);
// 8. Bottlenecks // 8. Bottlenecks
MarketingAnalysisResponseV2.Bottlenecks bottlenecks = new MarketingAnalysisResponseV2.Bottlenecks(); MarketingAnalysisResponseV2.Bottlenecks bottlenecks = new MarketingAnalysisResponseV2.Bottlenecks();
bottlenecks.setTitle("Узкие места"); bottlenecks.setTitle("Узкие места");
Map<String, String> statistics = new LinkedHashMap<>(); java.util.Map<String, String> statistics = new java.util.LinkedHashMap<>();
statistics.put("landing_page_issues", "25% (Отказы)"); statistics.put("landing_page_issues", "[[%]]");
statistics.put("slow_manager_responses", "30% (Потери)"); statistics.put("slow_manager_responses", "[[%]]");
statistics.put("repeat_interaction_refusal", "20% (Недожим)");
bottlenecks.setStatistics(statistics); bottlenecks.setStatistics(statistics);
MarketingAnalysisResponseV2.BottlenecksDetails details = new MarketingAnalysisResponseV2.BottlenecksDetails(); MarketingAnalysisResponseV2.BottlenecksDetails bDetails = new MarketingAnalysisResponseV2.BottlenecksDetails();
details.setLanding(List.of("Медленная загрузка", "Непонятное УТП", "Нет мобильной адаптации")); bDetails.setLanding(java.util.List.of("[[ПРОБЛЕМА_1]]", "[[ПРОБЛЕМА_2]]"));
details.setLeadManagement(List.of("Долгий ответ (>15 мин)", "Нет скриптов", "Нет CRM")); bDetails.setLeadManagement(java.util.List.of("[[ПРОБЛЕМА_1]]"));
details.setRetention(List.of("Нет программы лояльности", "Редкие касания базы", "Высокая цена без ценности")); bDetails.setRetention(java.util.List.of("[[ПРОБЛЕМА_1]]"));
bottlenecks.setDetails(details); bottlenecks.setDetails(bDetails);
bottlenecks.setConclusion("[[ВЫВОД_О_ПРОБЛЕМАХ]]");
bottlenecks.setConclusion("Технические проблемы сайта и отсутствие CRM — главные барьеры.");
sections.setBottlenecks(bottlenecks); sections.setBottlenecks(bottlenecks);
// 9. Recommendations // 9. Recommendations
MarketingAnalysisResponseV2.Recommendations recommendations = new MarketingAnalysisResponseV2.Recommendations(); MarketingAnalysisResponseV2.Recommendations recommendations = new MarketingAnalysisResponseV2.Recommendations();
recommendations.setTitle("Рекомендации"); recommendations.setTitle("Рекомендации");
recommendations.setCorePillars(List.of( recommendations.setCorePillars(java.util.List.of(
new MarketingAnalysisResponseV2.CorePillar("Быстрый старт", "Запустить таргет с лид-магнитом."), new MarketingAnalysisResponseV2.CorePillar("Быстрый старт", "[[ДЕЙСТВИЕ_1]]"),
new MarketingAnalysisResponseV2.CorePillar("Оптимизация", "Внедрить CRM и скрипты продаж."), new MarketingAnalysisResponseV2.CorePillar("Оптимизация", "[[ДЕЙСТВИЕ_2]]"),
new MarketingAnalysisResponseV2.CorePillar("Масштабирование", "Подключить TikTok Ads и блогеров."), new MarketingAnalysisResponseV2.CorePillar("Масштабирование", "[[ДЕЙСТВИЕ_3]]"),
new MarketingAnalysisResponseV2.CorePillar("Удержание", "Внедрить систему лояльности.") new MarketingAnalysisResponseV2.CorePillar("Удержание", "[[ДЕЙСТВИЕ_4]]")
)); ));
recommendations.setConclusion("Реализация плана позволит удвоить количество лидов за 3 месяца."); recommendations.setConclusion("[[ФИНАЛЬНЫЙ_СОВЕТ]]");
sections.setRecommendations(recommendations); sections.setRecommendations(recommendations);
response.setSections(sections); response.setSections(sections);