fix
This commit is contained in:
@@ -53,7 +53,7 @@ public class MarketingAnalysisRequest {
|
||||
|
||||
@Size(max = 500, message = "Поле 'weakSide' должно содержать не более 500 символов")
|
||||
private String weakSide;
|
||||
|
||||
|
||||
@NotEmpty(message = "Поле 'analysisType' обязательно для заполнения. Выберите хотя бы один тип анализа")
|
||||
@ValidAnalysisType(message = "Поле 'analysisType' должно содержать один или несколько типов: РЫНОК, КОНКУРЕНТЫ, ЦА, КАНАЛЫ, SWOT")
|
||||
private List<String> analysisType;
|
||||
|
||||
@@ -128,8 +128,7 @@ public class MarketingAnalysisResponseV2 {
|
||||
public static class CompetitiveEnvironment {
|
||||
private String title;
|
||||
private CompetitiveEnvironmentMetrics metrics;
|
||||
private Map<String, Integer> digitalVisibilityBenchmarks;
|
||||
private Map<String, List<String>> competitorStrengths;
|
||||
private List<CompetitorRow> competitors;
|
||||
private String conclusion;
|
||||
}
|
||||
|
||||
@@ -139,11 +138,23 @@ public class MarketingAnalysisResponseV2 {
|
||||
@NoArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class CompetitiveEnvironmentMetrics {
|
||||
private String marketShareTraffic;
|
||||
private String visibilityRank;
|
||||
private String competitorShareInContextAds;
|
||||
private String benchmarkAstana;
|
||||
private String leaderMarketShare;
|
||||
private String competitionLevel;
|
||||
private String adPressure;
|
||||
private String marketLeaderName;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class CompetitorRow {
|
||||
private String name;
|
||||
private String website;
|
||||
private Integer visibilityScore;
|
||||
private List<String> keyStrengths;
|
||||
private String priceSegment;
|
||||
}
|
||||
|
||||
@Setter
|
||||
|
||||
@@ -635,38 +635,57 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
|
||||
public MarketingAnalysisResponseV2 generateAnalysisV2(MarketingAnalysisRequest request) {
|
||||
logger.info("Starting Marketing Analysis V2 (Big 4 Standard) for request: {}", request);
|
||||
logger.info(">>> Starting Marketing Analysis V2 for Niche: '{}', Region: '{}'",
|
||||
request.getBusinessNiche(), request.getRegion());
|
||||
|
||||
try {
|
||||
// 1. Сбор данных
|
||||
// -------------------------------------------------------
|
||||
// 1. Сбор данных (Research Phase)
|
||||
// -------------------------------------------------------
|
||||
// Собираем данные из Google/SerpApi, чтобы у GPT был контекст (реальные сайты, статьи)
|
||||
Map<String, Object> researchPack = buildResearchPack(request);
|
||||
|
||||
// Пытаемся найти 3-5 реальных конкурентов через поиск для подстановки в промпт
|
||||
List<String> competitorCompanies = resolveCompetitorCompaniesForBenchmarks(request, researchPack, 5);
|
||||
researchPack.put("competitor_companies", competitorCompanies);
|
||||
|
||||
// 2. Пример JSON (Обновлен под КЗ)
|
||||
// -------------------------------------------------------
|
||||
// 2. Формирование "Идеального примера" (Structure & Benchmarks)
|
||||
// -------------------------------------------------------
|
||||
// Мы создаем Java-объект с "умными" рыбами (бенчмарками), который GPT будет использовать как шаблон.
|
||||
// Это гарантирует, что структура JSON будет 1-в-1 как ждет фронтенд.
|
||||
MarketingAnalysisResponseV2 exampleDto = buildAnalysisV2ExampleForPrompt(request, competitorCompanies);
|
||||
|
||||
// Сериализуем пример в строку, чтобы вставить в промпт
|
||||
String structureExampleJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(exampleDto);
|
||||
|
||||
// 3. Формирование промпта (Безопасный метод!)
|
||||
// -------------------------------------------------------
|
||||
// 3. Формирование Промпта (System & User Instructions)
|
||||
// -------------------------------------------------------
|
||||
String systemPrompt = buildSystemPromptForKazakhstan();
|
||||
String instruction = buildDetailedInstruction(request, structureExampleJson);
|
||||
|
||||
// Собираем весь контекст для отправки
|
||||
String contextJson = objectMapper.writeValueAsString(Map.of(
|
||||
"request", request,
|
||||
"evidence", researchPack
|
||||
"user_request", request,
|
||||
"market_evidence_from_search", researchPack
|
||||
));
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 4. Вызов AI с механизмом Retry (на случай битого JSON)
|
||||
// -------------------------------------------------------
|
||||
for (int attempt = 1; attempt <= 2; attempt++) {
|
||||
try {
|
||||
// Вызов AI
|
||||
logger.info("Sending request to OpenAI (Attempt {}/2)...", attempt);
|
||||
|
||||
String aiResponse = openAIAnalyticsService.generateWithInstructionWithModel(
|
||||
contextJson,
|
||||
instruction,
|
||||
"ru",
|
||||
textModelName, // GPT-4o
|
||||
textModelName, // GPT-4o или o1-preview
|
||||
systemPrompt,
|
||||
16000,
|
||||
180000L
|
||||
16000, // Max tokens (увеличенный лимит для детального ответа)
|
||||
180000L // Timeout (3 минуты)
|
||||
);
|
||||
|
||||
if (aiResponse == null || aiResponse.isBlank()) {
|
||||
@@ -674,16 +693,19 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
|
||||
String cleaned = cleanJsonFromMarkdown(aiResponse);
|
||||
if (cleaned != null) cleaned = cleaned.replace("\uFEFF", "").trim();
|
||||
|
||||
|
||||
if (cleaned != null) {
|
||||
cleaned = cleaned.replace("\uFEFF", "").trim();
|
||||
}
|
||||
MarketingAnalysisResponseV2 parsed = objectMapper.readValue(cleaned, MarketingAnalysisResponseV2.class);
|
||||
replacePlaceholderCompanyKeysIfNeeded(parsed, competitorCompanies);
|
||||
|
||||
logger.info("<<< Analysis V2 generated successfully.");
|
||||
return parsed;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("JSON Parsing/Generation failed on attempt {}: {}", attempt, e.getMessage());
|
||||
if (attempt == 2) throw e;
|
||||
Thread.sleep(2000);
|
||||
if (attempt == 2) throw e; // Если второй раз не вышло — пробрасываем ошибку
|
||||
Thread.sleep(2000); // Пауза перед повтором
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Failed to generate valid analysis after retries.");
|
||||
@@ -706,66 +728,81 @@ public class MarketingAnalysisService {
|
||||
private String buildDetailedInstruction(MarketingAnalysisRequest request, String structureExampleJson) {
|
||||
String regionsList = (request.getRegion() != null && !request.getRegion().isEmpty())
|
||||
? String.join(", ", request.getRegion())
|
||||
: "Алматы, Астана, Шымкент";
|
||||
: "Алматы, Астана, Шымкент"; // Дефолтные города, если не выбраны
|
||||
|
||||
String ageRangesInstruction = "";
|
||||
if (request.getTargetAudience() != null && request.getTargetAudience().containsKey("ageRanges")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> ranges = (List<String>) request.getTargetAudience().get("ageRanges");
|
||||
if (ranges != null && !ranges.isEmpty()) {
|
||||
ageRangesInstruction = "Используй ТОЛЬКО эти возрастные группы: " + String.join(", ", ranges);
|
||||
ageRangesInstruction = "Для 'age_structure' и 'income_by_age' используй СТРОГО только эти возрастные группы: " + String.join(", ", ranges) + ".";
|
||||
}
|
||||
}
|
||||
|
||||
// Шаблон с плейсхолдерами {{KEY}} вместо %s
|
||||
String template = """
|
||||
Твоя задача: Провести глубокий маркетинговый анализ и вернуть его СТРОГО в формате JSON.
|
||||
|
||||
ВХОДНЫЕ ДАННЫЕ:
|
||||
Ниша: {{NICHE}}
|
||||
Продукт: {{PRODUCT}}
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
КРИТИЧЕСКИЕ ИНСТРУКЦИИ ПО КАЗАХСТАНУ (KZ):
|
||||
|
||||
1. ГЕОГРАФИЯ (market_overview.geography):
|
||||
- Ты ОБЯЗАН включить в анализ ВСЕ следующие города: [{{REGIONS}}].
|
||||
- Не ограничивайся тремя городами, если в списке их больше.
|
||||
- Значения (0-100) должны быть разными и неровными (например, 87, 64, 43).
|
||||
|
||||
2. КОНКУРЕНТЫ (competitive_environment):
|
||||
- Найди в `evidence` РЕАЛЬНЫХ лидеров рынка (сети, крупные игроки из 2GIS/Kaspi).
|
||||
- Игнорируй мелкие ИП или выдуманные названия.
|
||||
- Заполни `digital_visibility_benchmarks` реальными именами.
|
||||
|
||||
3. КАНАЛЫ (acquisition_channels):
|
||||
- Приоритет в РК: Instagram (Reels), TikTok, Kaspi Marketing, 2GIS.
|
||||
- Яндекс.Директ и VK ставь ниже (Low/Medium), если это B2C.
|
||||
- Указывай стоимость лида (CPL) строкой с валютой (напр. "2350 KZT").
|
||||
|
||||
4. ЦИФРЫ И ТИПЫ ДАННЫХ (КРИТИЧНО!):
|
||||
- `income_by_age` (TargetAudience): ЭТО ЧИСЛОВОЕ ПОЛЕ (Integer).
|
||||
Пиши ТОЛЬКО ГОЛЫЕ ЦИФРЫ. Без кавычек, без запятых, без пробелов, без "KZT".
|
||||
ПРАВИЛЬНО: 342500
|
||||
ОШИБКА: "342,500", "342 500", "342500 KZT".
|
||||
- `monthly_online_index`: Дробные числа (0.0 - 1.0).
|
||||
- `marketSize` (String): Пиши текстом с валютой ("500 млн KZT").
|
||||
- ЗАПРЕЩЕНО использовать круглые числа в статистике (20%, 50%, 1000). Пиши: 18.7%, 42.3%, 1240.
|
||||
|
||||
5. ИСТОЧНИКИ:
|
||||
- Собери ВСЕ ссылки из `evidence` в ОДИН СПИСОК в самом конце (поле `recommendations.conclusion`).
|
||||
- Не вставляй ссылки внутрь текста разделов.
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
{{AGE_INSTRUCTION}}
|
||||
|
||||
ФОРМАТ ОТВЕТА:
|
||||
- Только валидный JSON.
|
||||
- Заполни все поля `EXAMPLE__` и `-9999` реальными данными.
|
||||
|
||||
ЭТАЛОННАЯ СТРУКТУРА:
|
||||
{{JSON_STRUCTURE}}
|
||||
""";
|
||||
Твоя роль: Ты — Ведущий Маркетинговый Аналитик с опытом работы в Big4 (KPMG, PwC) по рынку Казахстана.
|
||||
Твоя задача: Провести глубокий, честный и критический анализ рынка для реального бизнеса. Верни результат СТРОГО в формате JSON.
|
||||
|
||||
ВХОДНЫЕ ДАННЫЕ:
|
||||
Ниша: {{NICHE}}
|
||||
Продукт/Услуга: {{PRODUCT}}
|
||||
Целевой регион: {{REGIONS}}
|
||||
|
||||
================================================================================
|
||||
СТРОГИЕ ИНСТРУКЦИИ ПО ЗАПОЛНЕНИЮ (NO HALLUCINATIONS POLICY):
|
||||
|
||||
1. **Market Overview (Обзор рынка):**
|
||||
- `online_demand_index`: НЕ ПИШИ ПРОСТО "Высокий". Пиши "Высокий (8/10)" или "Средний (растущий)".
|
||||
- `demand_growth_rate_yoy`: Используй некруглые числа (напр., "+12.4%", а не "10%").
|
||||
- `geography`:
|
||||
- ОБЯЗАТЕЛЬНО включи в ответ ВСЕ города из списка: [{{REGIONS}}].
|
||||
- Если список городов пуст или содержит "Весь Казахстан", добавь топ-5 городов (Алматы, Астана, Шымкент, Караганда, Актобе).
|
||||
- Значения (Integer 0-100) должны отражать РЕАЛЬНЫЙ спрос (Алматы всегда выше регионов). Разброс должен быть реалистичным (не все по 100).
|
||||
|
||||
2. **Target Audience (Целевая аудитория):**
|
||||
- `income_by_age`: ЭТО ЧИСЛОВОЕ ПОЛЕ (Integer). Пиши ТОЛЬКО ЦИФРЫ (средняя ЗП в тенге).
|
||||
- ПРАВИЛЬНО: 350000
|
||||
- ОШИБКА: "350 000", "350k KZT".
|
||||
- Ориентируйся на реальные зарплаты в РК (200k - 600k KZT).
|
||||
- `metrics.total_audience_size`: Пиши текстом, но реалистично (напр. "450-500 тыс. человек"), а не "10 млн".
|
||||
|
||||
3. **Competitive Environment (Конкуренты - КРИТИЧНО!):**
|
||||
- В поле `competitors` (массив объектов) найди 3-5 РЕАЛЬНЫХ конкурентов в Казахстане.
|
||||
- **НАЗВАНИЯ (`name`):** Пиши ТОЛЬКО БРЕНД.
|
||||
- ПРАВИЛЬНО: "Invictus", "World Class", "1Fit", "Kaspi Shop".
|
||||
- ОШИБКА: "ТОО Invictus Group", "ИП Иванов", "Фитнес клуб Ромашка".
|
||||
- **САЙТЫ (`website`):** Если знаешь реальный сайт, пиши его (напр. "invictus.kz"). Если нет — ищи профиль в Instagram ("instagram.com/invictus.kz").
|
||||
- `visibility_score`: Оценка от 0 до 100. Лидеры рынка > 80.
|
||||
- `metrics.leader_market_share`: Оцени долю лидера (напр. "35%").
|
||||
|
||||
4. **Acquisition Channels (Каналы):**
|
||||
- В Казахстане (B2C) доминируют: Instagram (Reels/Stories), TikTok, Kaspi Marketing (для товаров), 2GIS (для оффлайн-точек).
|
||||
- `average_lead_cost`: Пиши текстом с валютой ("2500 KZT"). Оценивай реально: фитнес ~3000-5000тг, товары ~1500-3000тг.
|
||||
|
||||
5. **Marketing Economics (Экономика - Бюджет до 3 млн KZT):**
|
||||
- Мы рассчитываем модель для малого/среднего бизнеса.
|
||||
- `marketing_expenses`: Указывай бюджет в диапазоне 500 000 - 3 000 000 KZT в месяц. НЕ БОЛЬШЕ.
|
||||
- `cac` (Стоимость клиента): Должна быть адекватной (не 50 тенге).
|
||||
- `roi`: Реалистичный (100% - 300%), а не 5000%.
|
||||
|
||||
6. **Customer Journey (Воронка):**
|
||||
- `funnel_metrics`: Используй реалистичную воронку.
|
||||
- Traffic: ~10 000 - 50 000
|
||||
- Leads: ~2% - 5% от трафика
|
||||
- Sales: ~10% - 30% от лидов.
|
||||
|
||||
7. **Источники (Sources):**
|
||||
- Собери РЕАЛЬНЫЕ ссылки (Forbes.kz, Kapital.kz, Stat.gov.kz, сайты конкурентов) в массив `sources`.
|
||||
|
||||
================================================================================
|
||||
{{AGE_INSTRUCTION}}
|
||||
|
||||
ФОРМАТ ОТВЕТА: ТОЛЬКО JSON. Без Markdown, без "```json".
|
||||
Используй структуру ниже как ОБРАЗЕЦ, но заполни её СВОИМИ ДАННЫМИ для ниши {{NICHE}}.
|
||||
|
||||
ОБРАЗЕЦ СТРУКТУРЫ (JSON):
|
||||
{{JSON_STRUCTURE}}
|
||||
""";
|
||||
|
||||
return template
|
||||
.replace("{{NICHE}}", request.getBusinessNiche())
|
||||
@@ -776,21 +813,182 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
private String buildJsonStructureExample() {
|
||||
return """
|
||||
{
|
||||
"report_title": "Стратегический анализ рынка...",
|
||||
"sources": ["https://stat.gov.kz/...", "https://forbes.kz/..."],
|
||||
"sections": {
|
||||
"market_overview": { "metrics": { "online_demand_index": "0.8" }, "conclusion": "Текст..." },
|
||||
"acquisition_channels": {
|
||||
"channel_shares": { "Instagram": "40%", "Kaspi Shop": "30%", "Google SEO": "20%" },
|
||||
"conclusion": "Текст..."
|
||||
{
|
||||
"report_title": "Стратегический анализ рынка: [Ниша]",
|
||||
"sources": [
|
||||
"[https://stat.gov.kz/](https://stat.gov.kz/)...",
|
||||
"[https://forbes.kz/](https://forbes.kz/)...",
|
||||
"[https://kapital.kz/](https://kapital.kz/)..."
|
||||
],
|
||||
"sections": {
|
||||
"market_overview": {
|
||||
"title": "Обзор рынка",
|
||||
"metrics": {
|
||||
"online_demand_index": "Высокий (8/10)",
|
||||
"online_potential": "Растущий (+15% YoY)",
|
||||
"demand_growth_rate_yoy": "+18.5%",
|
||||
"seasonality_coefficient_spring": "1.2",
|
||||
"seasonality_range": "Сентябрь - Май"
|
||||
},
|
||||
"monthly_online_index": {
|
||||
"january": 0.8, "february": 0.9, "march": 1.0, "april": 0.9, "may": 0.8,
|
||||
"june": 0.6, "july": 0.5, "august": 0.7, "september": 0.9,
|
||||
"october": 0.95, "november": 1.0, "december": 0.9
|
||||
},
|
||||
"geography": {
|
||||
"Алматы": 100,
|
||||
"Астана": 85,
|
||||
"Шымкент": 50
|
||||
},
|
||||
"conclusion": "Рынок демонстрирует устойчивый рост..."
|
||||
},
|
||||
"target_audience": {
|
||||
"title": "Портрет аудитории",
|
||||
"metrics": {
|
||||
"total_audience_size": "~450 000 чел.",
|
||||
"leading_segment_share": "Женщины 25-34",
|
||||
"above_average_income_share": "35%"
|
||||
},
|
||||
"income_by_age": {
|
||||
"25-34": 350000,
|
||||
"35-44": 420000
|
||||
},
|
||||
"segments": [
|
||||
{ "name": "Ядро (Core)", "share": "45%" },
|
||||
{ "name": "Потенциальные", "share": "30%" }
|
||||
],
|
||||
"age_structure": {
|
||||
"25-34": "45%",
|
||||
"35-44": "30%"
|
||||
},
|
||||
"conclusion": "Ключевой сегмент - платежеспособные специалисты..."
|
||||
},
|
||||
"competitive_environment": {
|
||||
"title": "Конкурентная среда",
|
||||
"metrics": {
|
||||
"leader_market_share": "35%",
|
||||
"competition_level": "Высокая",
|
||||
"ad_pressure": "Агрессивная",
|
||||
"market_leader_name": "Invictus"
|
||||
},
|
||||
"competitors": [
|
||||
{
|
||||
"name": "Invictus",
|
||||
"website": "invictus.kz",
|
||||
"visibility_score": 95,
|
||||
"key_strengths": ["Сильный бренд", "Сеть залов"],
|
||||
"price_segment": "Premium"
|
||||
},
|
||||
"recommendations": { "core_pillars": [ { "strategy": "...", "action": "..." } ] }
|
||||
}
|
||||
{
|
||||
"name": "1Fit",
|
||||
"website": "1fit.app",
|
||||
"visibility_score": 88,
|
||||
"key_strengths": ["Единый абонемент", "Удобное приложение"],
|
||||
"price_segment": "Aggregator"
|
||||
}
|
||||
],
|
||||
"conclusion": "Рынок консолидирован крупными игроками..."
|
||||
},
|
||||
"demand_and_behavior": {
|
||||
"title": "Поведение потребителей",
|
||||
"metrics": {
|
||||
"search_volume_growth": "+22%",
|
||||
"interactions_before_purchase": "5-7 касаний",
|
||||
"repeat_refusal_probability": "15%"
|
||||
},
|
||||
"search_query_types": {
|
||||
"Коммерческие": "45%",
|
||||
"Информационные": "30%"
|
||||
},
|
||||
"decision_factors": {
|
||||
"Цена": "Высокая важность",
|
||||
"Отзывы": "Средняя важность"
|
||||
},
|
||||
"reasons_for_refusal": {
|
||||
"Высокая цена": "40%",
|
||||
"Плохой сервис": "30%"
|
||||
},
|
||||
"conclusion": "Пользователи активно сравнивают цены..."
|
||||
},
|
||||
"customer_journey": {
|
||||
"title": "Воронка продаж",
|
||||
"funnel_metrics": {
|
||||
"ad_traffic": "15 000 кликов",
|
||||
"site_visitors": "12 000 (CTR 1.5%)",
|
||||
"leads": 450,
|
||||
"bookings": 200,
|
||||
"payments": 120
|
||||
},
|
||||
"conversion_rates": {
|
||||
"lead_to_booking": "45%",
|
||||
"booking_to_payment": "60%",
|
||||
"lead_to_payment_total": "27%"
|
||||
},
|
||||
"funnel_bottlenecks": {
|
||||
"lead_to_booking": "Долгий ответ менеджера",
|
||||
"booking_to_payment": "Нет рассрочки"
|
||||
},
|
||||
"conclusion": "Основная потеря лидов происходит на этапе..."
|
||||
},
|
||||
"acquisition_channels": {
|
||||
"title": "Каналы привлечения",
|
||||
"metrics": {
|
||||
"search_traffic_share": "30%",
|
||||
"average_lead_cost": "3500 KZT",
|
||||
"growth_potential": "TikTok/Reels"
|
||||
},
|
||||
"channel_shares": {
|
||||
"Instagram": "50%",
|
||||
"Google Ads": "20%",
|
||||
"TikTok": "15%"
|
||||
},
|
||||
"channel_efficiency": {
|
||||
"instagram": { "cost": "2500 KZT", "status": "High" },
|
||||
"google_search": { "cost": "4500 KZT", "status": "Medium" }
|
||||
},
|
||||
"conclusion": "Instagram остается самым эффективным каналом..."
|
||||
},
|
||||
"marketing_economics": {
|
||||
"title": "Юнит-экономика",
|
||||
"metrics": {
|
||||
"customer_acquisition_cost_cac": "3500 KZT",
|
||||
"repeat_sales_conversion": "30%",
|
||||
"roi": "250%"
|
||||
},
|
||||
"financial_performance": {
|
||||
"marketing_expenses": { "month": "1.5 млн KZT", "year": "18 млн KZT" },
|
||||
"clients": { "month": "~430", "year": "~5160" },
|
||||
"cac": { "month": "3500 KZT", "year": "3200 KZT" },
|
||||
"revenue_from_client": { "month": "25 000 KZT", "year": "300 000 KZT" },
|
||||
"profit_from_client": { "month": "7 500 KZT", "year": "90 000 KZT" }
|
||||
},
|
||||
"conclusion": "Модель масштабируема при сохранении CAC..."
|
||||
},
|
||||
"bottlenecks": {
|
||||
"title": "Узкие места",
|
||||
"statistics": {
|
||||
"landing_page_issues": "Отказы 60%",
|
||||
"slow_manager_responses": "30% потерь"
|
||||
},
|
||||
"details": {
|
||||
"landing": ["Медленная загрузка", "Слабый оффер"],
|
||||
"lead_management": ["Нет скриптов", "Звонок > 15 мин"],
|
||||
"retention": ["Нет программы лояльности"]
|
||||
},
|
||||
"conclusion": "Необходимо оптимизировать мобильную версию сайта..."
|
||||
},
|
||||
"recommendations": {
|
||||
"title": "План действий",
|
||||
"core_pillars": [
|
||||
{ "strategy": "Быстрый старт", "action": "Запустить таргет..." },
|
||||
{ "strategy": "Удержание", "action": "Внедрить систему бонусов..." }
|
||||
],
|
||||
"conclusion": "Рекомендуется начать с..."
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
private boolean isPlaceholderCompetitorKey(String key) {
|
||||
if (key == null) {
|
||||
return false;
|
||||
@@ -1002,7 +1200,8 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
|
||||
private void replacePlaceholderCompanyKeysIfNeeded(MarketingAnalysisResponseV2 parsed,
|
||||
List<String> competitorCompanies) {
|
||||
List<String> competitorCompanies) {
|
||||
// 1. Базовые проверки на null
|
||||
if (parsed == null || competitorCompanies == null || competitorCompanies.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -1012,31 +1211,35 @@ public class MarketingAnalysisService {
|
||||
|
||||
MarketingAnalysisResponseV2.CompetitiveEnvironment ce = parsed.getSections().getCompetitiveEnvironment();
|
||||
|
||||
Map<String, Integer> dvb = ce.getDigitalVisibilityBenchmarks();
|
||||
if (dvb != null && !dvb.isEmpty()) {
|
||||
boolean hasPlaceholders = dvb.keySet().stream().anyMatch(this::isPlaceholderCompetitorKey);
|
||||
if (hasPlaceholders) {
|
||||
List<Integer> values = new ArrayList<>(dvb.values());
|
||||
LinkedHashMap<String, Integer> rewritten = new LinkedHashMap<>();
|
||||
int n = Math.min(values.size(), competitorCompanies.size());
|
||||
for (int i = 0; i < n; i++) {
|
||||
rewritten.put(competitorCompanies.get(i), values.get(i));
|
||||
}
|
||||
ce.setDigitalVisibilityBenchmarks(rewritten);
|
||||
}
|
||||
}
|
||||
// Получаем список конкурентов из DTO
|
||||
List<MarketingAnalysisResponseV2.CompetitorRow> rows = ce.getCompetitors();
|
||||
|
||||
if (rows != null && !rows.isEmpty()) {
|
||||
// Проверяем, есть ли заглушки хотя бы у одного элемента
|
||||
boolean hasPlaceholders = rows.stream()
|
||||
.anyMatch(row -> isPlaceholderCompetitorKey(row.getName()));
|
||||
|
||||
Map<String, List<String>> strengths = ce.getCompetitorStrengths();
|
||||
if (strengths != null && !strengths.isEmpty()) {
|
||||
boolean hasPlaceholders = strengths.keySet().stream().anyMatch(this::isPlaceholderCompetitorKey);
|
||||
if (hasPlaceholders) {
|
||||
List<List<String>> values = new ArrayList<>(strengths.values());
|
||||
LinkedHashMap<String, List<String>> rewritten = new LinkedHashMap<>();
|
||||
int n = Math.min(values.size(), competitorCompanies.size());
|
||||
// Определяем, сколько элементов мы можем обновить (чтобы не выйти за пределы массивов)
|
||||
int n = Math.min(rows.size(), competitorCompanies.size());
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
rewritten.put(competitorCompanies.get(i), values.get(i));
|
||||
MarketingAnalysisResponseV2.CompetitorRow row = rows.get(i);
|
||||
|
||||
// Если имя текущего конкурента - заглушка, меняем на реальное
|
||||
if (isPlaceholderCompetitorKey(row.getName())) {
|
||||
String realCompetitorName = competitorCompanies.get(i);
|
||||
row.setName(realCompetitorName);
|
||||
|
||||
// ЛОГИКА ДЛЯ САЙТА:
|
||||
// Если у нас нет сайта от ГПТ, или если реальное имя похоже на домен,
|
||||
// можно записать его и в website для надежности.
|
||||
if (row.getWebsite() == null || row.getWebsite().isEmpty() || row.getWebsite().equals("—")) {
|
||||
// Предполагаем, что в competitorCompanies могут лежать домены
|
||||
row.setWebsite(realCompetitorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
ce.setCompetitorStrengths(rewritten);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1094,242 +1297,276 @@ public class MarketingAnalysisService {
|
||||
}
|
||||
|
||||
private MarketingAnalysisResponseV2 buildAnalysisV2ExampleForPrompt(MarketingAnalysisRequest request,
|
||||
List<String> competitorCompanies) {
|
||||
List<String> competitorCompanies) {
|
||||
MarketingAnalysisResponseV2 response = new MarketingAnalysisResponseV2();
|
||||
response.setReportTitle("EXAMPLE__REPORT_TITLE");
|
||||
response.setReportTitle("Стратегический анализ рынка: " + request.getBusinessNiche());
|
||||
|
||||
MarketingAnalysisResponseV2.Sections sections = new MarketingAnalysisResponseV2.Sections();
|
||||
|
||||
// 1. Market Overview
|
||||
MarketingAnalysisResponseV2.MarketOverview marketOverview = new MarketingAnalysisResponseV2.MarketOverview();
|
||||
marketOverview.setTitle("EXAMPLE__MARKET_OVERVIEW_TITLE");
|
||||
marketOverview.setTitle("Обзор рынка");
|
||||
|
||||
MarketingAnalysisResponseV2.MarketOverviewMetrics moMetrics = new MarketingAnalysisResponseV2.MarketOverviewMetrics();
|
||||
moMetrics.setOnlineDemandIndex("EXAMPLE__ONLINE_DEMAND_INDEX");
|
||||
moMetrics.setOnlinePotential("EXAMPLE__ONLINE_POTENTIAL");
|
||||
moMetrics.setDemandGrowthRateYoy("EXAMPLE__DEMAND_GROWTH_RATE_YOY");
|
||||
moMetrics.setSeasonalityCoefficientSpring("EXAMPLE__SEASONALITY_COEFFICIENT_SPRING");
|
||||
moMetrics.setSeasonalityRange("EXAMPLE__SEASONALITY_RANGE");
|
||||
moMetrics.setOnlineDemandIndex("Высокий (Активный рост)");
|
||||
moMetrics.setOnlinePotential("Растущий тренд (+15-20% YoY)");
|
||||
moMetrics.setDemandGrowthRateYoy("+18.5%");
|
||||
moMetrics.setSeasonalityCoefficientSpring("1.25 (Высокий сезон)");
|
||||
moMetrics.setSeasonalityRange("Сентябрь - Май");
|
||||
marketOverview.setMetrics(moMetrics);
|
||||
|
||||
MarketingAnalysisResponseV2.MonthlyOnlineIndex monthlyOnlineIndex = new MarketingAnalysisResponseV2.MonthlyOnlineIndex();
|
||||
monthlyOnlineIndex.setJanuary(-9999.0);
|
||||
monthlyOnlineIndex.setFebruary(-9999.0);
|
||||
monthlyOnlineIndex.setMarch(-9999.0);
|
||||
monthlyOnlineIndex.setApril(-9999.0);
|
||||
monthlyOnlineIndex.setMay(-9999.0);
|
||||
monthlyOnlineIndex.setJune(-9999.0);
|
||||
monthlyOnlineIndex.setJuly(-9999.0);
|
||||
monthlyOnlineIndex.setAugust(-9999.0);
|
||||
monthlyOnlineIndex.setSeptember(-9999.0);
|
||||
monthlyOnlineIndex.setOctober(-9999.0);
|
||||
monthlyOnlineIndex.setNovember(-9999.0);
|
||||
monthlyOnlineIndex.setDecember(-9999.0);
|
||||
// Реалистичная кривая сезонности (спад летом, рост к НГ и весне)
|
||||
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); // Black Friday
|
||||
monthlyOnlineIndex.setDecember(0.98);
|
||||
marketOverview.setMonthlyOnlineIndex(monthlyOnlineIndex);
|
||||
|
||||
// География (Проценты, сумма ~100, приоритет Алматы/Астана)
|
||||
marketOverview.setGeography(Map.of(
|
||||
"EXAMPLE__CITY_1", -9999,
|
||||
"EXAMPLE__CITY_2", -9999,
|
||||
"EXAMPLE__CITY_3", -9999));
|
||||
marketOverview.setConclusion("EXAMPLE__MARKET_OVERVIEW_CONCLUSION");
|
||||
"Алматы", 45,
|
||||
"Астана", 30,
|
||||
"Шымкент", 10,
|
||||
"Караганда", 8,
|
||||
"Актобе", 5,
|
||||
"Другие регионы", 2
|
||||
));
|
||||
marketOverview.setConclusion("Рынок находится в стадии активного роста. Основной спрос сконцентрирован в мегаполисах (75%), сезонность ярко выражена с пиками весной и осенью.");
|
||||
sections.setMarketOverview(marketOverview);
|
||||
|
||||
// 2. Target Audience
|
||||
MarketingAnalysisResponseV2.TargetAudience targetAudience = new MarketingAnalysisResponseV2.TargetAudience();
|
||||
targetAudience.setTitle("EXAMPLE__TARGET_AUDIENCE_TITLE");
|
||||
targetAudience.setTitle("Портрет целевой аудитории");
|
||||
|
||||
MarketingAnalysisResponseV2.TargetAudienceMetrics taMetrics = new MarketingAnalysisResponseV2.TargetAudienceMetrics();
|
||||
taMetrics.setTotalAudienceSize("EXAMPLE__TOTAL_AUDIENCE_SIZE");
|
||||
taMetrics.setLeadingSegmentShare("EXAMPLE__LEADING_SEGMENT_SHARE");
|
||||
taMetrics.setAboveAverageIncomeShare("EXAMPLE__ABOVE_AVERAGE_INCOME_SHARE");
|
||||
taMetrics.setTotalAudienceSize("~350 000 - 500 000 активных пользователей");
|
||||
taMetrics.setLeadingSegmentShare("Женщины 25-34 года");
|
||||
taMetrics.setAboveAverageIncomeShare("42% (Средний+)");
|
||||
targetAudience.setMetrics(taMetrics);
|
||||
|
||||
targetAudience.setSegments(List.of(
|
||||
new MarketingAnalysisResponseV2.AudienceSegment("EXAMPLE__AUDIENCE_SEGMENT_1",
|
||||
"EXAMPLE__AUDIENCE_SEGMENT_SHARE_1"),
|
||||
new MarketingAnalysisResponseV2.AudienceSegment("EXAMPLE__AUDIENCE_SEGMENT_2",
|
||||
"EXAMPLE__AUDIENCE_SEGMENT_SHARE_2"),
|
||||
new MarketingAnalysisResponseV2.AudienceSegment("EXAMPLE__AUDIENCE_SEGMENT_3",
|
||||
"EXAMPLE__AUDIENCE_SEGMENT_SHARE_3")));
|
||||
new MarketingAnalysisResponseV2.AudienceSegment("Ядро (Core)", "45% - Покупают регулярно"),
|
||||
new MarketingAnalysisResponseV2.AudienceSegment("Потенциальные", "30% - Сравнивают цены"),
|
||||
new MarketingAnalysisResponseV2.AudienceSegment("Ситуативные", "25% - Ищут акции")
|
||||
));
|
||||
|
||||
// Возрастная структура (Реальная демография активных покупателей)
|
||||
Map<String, String> ageStructure = new LinkedHashMap<>();
|
||||
Map<String, Object> targetAudienceData = request.getTargetAudience();
|
||||
if (targetAudienceData != null && targetAudienceData.containsKey("ageRanges")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> ageRanges = (List<String>) targetAudienceData.get("ageRanges");
|
||||
if (ageRanges != null && !ageRanges.isEmpty()) {
|
||||
for (String ageRange : ageRanges) {
|
||||
String normalizedKey = ageRange.replace("+", "_plus");
|
||||
String placeholderKey = normalizedKey.replace("-", "_").toUpperCase();
|
||||
ageStructure.put(normalizedKey, "EXAMPLE__AGE_" + placeholderKey + "_SHARE");
|
||||
}
|
||||
} else {
|
||||
ageStructure.put("25-34", "EXAMPLE__AGE_25_34_SHARE");
|
||||
ageStructure.put("35-44", "EXAMPLE__AGE_35_44_SHARE");
|
||||
}
|
||||
} else {
|
||||
ageStructure.put("25-34", "EXAMPLE__AGE_25_34_SHARE");
|
||||
ageStructure.put("35-44", "EXAMPLE__AGE_35_44_SHARE");
|
||||
}
|
||||
ageStructure.put("18-24", "15%");
|
||||
ageStructure.put("25-34", "45%"); // Платежеспособное ядро
|
||||
ageStructure.put("35-44", "25%");
|
||||
ageStructure.put("45+", "15%");
|
||||
targetAudience.setAgeStructure(ageStructure);
|
||||
targetAudience.setConclusion("EXAMPLE__TARGET_AUDIENCE_CONCLUSION");
|
||||
|
||||
// Доходы (Медиана по РК для среднего класса)
|
||||
Map<String, Integer> incomeByAge = new LinkedHashMap<>();
|
||||
incomeByAge.put("18-24", 180000);
|
||||
incomeByAge.put("25-34", 350000);
|
||||
incomeByAge.put("35-44", 420000);
|
||||
incomeByAge.put("45+", 300000);
|
||||
targetAudience.setIncomeByAge(incomeByAge);
|
||||
|
||||
targetAudience.setConclusion("Ключевой клиент — специалист/менеджер 25-35 лет с доходом выше среднего, ценит сервис и скорость. Вторичный сегмент — студенты и молодежь (чувствительны к цене).");
|
||||
sections.setTargetAudience(targetAudience);
|
||||
|
||||
// 3. Competitive Environment (ОБНОВЛЕННАЯ СТРУКТУРА)
|
||||
MarketingAnalysisResponseV2.CompetitiveEnvironment competitiveEnvironment = new MarketingAnalysisResponseV2.CompetitiveEnvironment();
|
||||
competitiveEnvironment.setTitle("EXAMPLE__COMPETITIVE_ENVIRONMENT_TITLE");
|
||||
competitiveEnvironment.setTitle("Конкурентный ландшафт");
|
||||
|
||||
MarketingAnalysisResponseV2.CompetitiveEnvironmentMetrics ceMetrics = new MarketingAnalysisResponseV2.CompetitiveEnvironmentMetrics();
|
||||
ceMetrics.setMarketShareTraffic("EXAMPLE__MARKET_SHARE_TRAFFIC");
|
||||
ceMetrics.setVisibilityRank("EXAMPLE__VISIBILITY_RANK");
|
||||
ceMetrics.setCompetitorShareInContextAds("EXAMPLE__COMPETITOR_SHARE_IN_CONTEXT_ADS");
|
||||
ceMetrics.setBenchmarkAstana("EXAMPLE__BENCHMARK_ASTANA");
|
||||
ceMetrics.setLeaderMarketShare("45-50%"); // Доля топ-игроков
|
||||
ceMetrics.setCompetitionLevel("Высокая (Красный океан)");
|
||||
ceMetrics.setAdPressure("Агрессивная (Высокие ставки CPI)");
|
||||
ceMetrics.setMarketLeaderName("Сетевые лидеры / Маркетплейсы");
|
||||
competitiveEnvironment.setMetrics(ceMetrics);
|
||||
Map<String, Integer> digitalVisibilityBenchmarks = new LinkedHashMap<>();
|
||||
List<String> companies = (competitorCompanies != null) ? competitorCompanies : List.of();
|
||||
if (companies.isEmpty()) {
|
||||
companies = List.of("Компания 1", "Компания 2", "Компания 3", "Компания 4", "Компания 5");
|
||||
}
|
||||
|
||||
// Список конкурентов (для таблицы и графика)
|
||||
List<MarketingAnalysisResponseV2.CompetitorRow> competitorRows = new java.util.ArrayList<>();
|
||||
List<String> companies = (competitorCompanies != null && !competitorCompanies.isEmpty())
|
||||
? competitorCompanies
|
||||
: List.of("Крупный сетевой бренд", "Агрессивный дискаунтер", "Премиум-игрок");
|
||||
|
||||
// Заполняем 3-5 конкурентов данными для примера
|
||||
for (int i = 0; i < Math.min(companies.size(), 5); i++) {
|
||||
digitalVisibilityBenchmarks.put(companies.get(i), -9999);
|
||||
MarketingAnalysisResponseV2.CompetitorRow row = new MarketingAnalysisResponseV2.CompetitorRow();
|
||||
row.setName(companies.get(i));
|
||||
row.setWebsite("https://google.com/search?q=" + companies.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.setDigitalVisibilityBenchmarks(digitalVisibilityBenchmarks);
|
||||
Map<String, List<String>> competitorStrengths = new LinkedHashMap<>();
|
||||
for (int i = 0; i < Math.min(companies.size(), 5); i++) {
|
||||
competitorStrengths.put(companies.get(i),
|
||||
List.of("EXAMPLE__COMPETITOR_STRENGTH_1", "EXAMPLE__COMPETITOR_STRENGTH_2"));
|
||||
}
|
||||
competitiveEnvironment.setCompetitorStrengths(competitorStrengths);
|
||||
competitiveEnvironment.setConclusion("EXAMPLE__COMPETITIVE_ENVIRONMENT_CONCLUSION");
|
||||
competitiveEnvironment.setCompetitors(competitorRows);
|
||||
|
||||
competitiveEnvironment.setConclusion("Рынок консолидирован крупными игроками. Для входа требуется четкое УТП или узкая нишевание, прямая ценовая конкуренция неэффективна.");
|
||||
sections.setCompetitiveEnvironment(competitiveEnvironment);
|
||||
|
||||
// 4. Demand And Behavior
|
||||
MarketingAnalysisResponseV2.DemandAndBehavior demandAndBehavior = new MarketingAnalysisResponseV2.DemandAndBehavior();
|
||||
demandAndBehavior.setTitle("EXAMPLE__DEMAND_AND_BEHAVIOR_TITLE");
|
||||
demandAndBehavior.setTitle("Паттерны поведения");
|
||||
|
||||
MarketingAnalysisResponseV2.DemandAndBehaviorMetrics dabMetrics = new MarketingAnalysisResponseV2.DemandAndBehaviorMetrics();
|
||||
dabMetrics.setSearchVolumeGrowth("EXAMPLE__SEARCH_VOLUME_GROWTH");
|
||||
dabMetrics.setInteractionsBeforePurchase("EXAMPLE__INTERACTIONS_BEFORE_PURCHASE");
|
||||
dabMetrics.setRepeatRefusalProbability("EXAMPLE__REPEAT_REFUSAL_PROBABILITY");
|
||||
dabMetrics.setSearchVolumeGrowth("+22% YoY");
|
||||
dabMetrics.setInteractionsBeforePurchase("5-7 касаний");
|
||||
dabMetrics.setRepeatRefusalProbability("~15% (Сравнение цен)");
|
||||
demandAndBehavior.setMetrics(dabMetrics);
|
||||
|
||||
Map<String, String> searchQueryTypes = new LinkedHashMap<>();
|
||||
searchQueryTypes.put("direct_interest_catalog", "EXAMPLE__QUERY_TYPE_DIRECT");
|
||||
searchQueryTypes.put("question_based_articles", "EXAMPLE__QUERY_TYPE_QUESTION");
|
||||
searchQueryTypes.put("competitors_reviews", "EXAMPLE__QUERY_TYPE_COMPETITORS");
|
||||
searchQueryTypes.put("problem_solving_hot_promo", "EXAMPLE__QUERY_TYPE_PROMO");
|
||||
searchQueryTypes.put("Коммерческие (купить, цена)", "45%");
|
||||
searchQueryTypes.put("Информационные (отзывы, как выбрать)", "30%");
|
||||
searchQueryTypes.put("Брендовые (имя конкурента)", "15%");
|
||||
searchQueryTypes.put("Гео-запросы (рядом, адрес)", "10%");
|
||||
demandAndBehavior.setSearchQueryTypes(searchQueryTypes);
|
||||
|
||||
Map<String, String> decisionFactors = new LinkedHashMap<>();
|
||||
decisionFactors.put("promo_discount", "EXAMPLE__FACTOR_PROMO");
|
||||
decisionFactors.put("review_recommendation", "EXAMPLE__FACTOR_REVIEWS");
|
||||
decisionFactors.put("warranty_quality", "EXAMPLE__FACTOR_WARRANTY");
|
||||
decisionFactors.put("convenience_service", "EXAMPLE__FACTOR_CONVENIENCE");
|
||||
decisionFactors.put("availability", "EXAMPLE__FACTOR_AVAILABILITY");
|
||||
decisionFactors.put("price", "EXAMPLE__FACTOR_PRICE");
|
||||
decisionFactors.put("Цена / Рассрочка", "35%");
|
||||
decisionFactors.put("Отзывы (Social Proof)", "25%");
|
||||
decisionFactors.put("Скорость / Сервис", "20%");
|
||||
decisionFactors.put("Личный бренд / Доверие", "15%");
|
||||
decisionFactors.put("Удобство оплаты", "5%");
|
||||
demandAndBehavior.setDecisionFactors(decisionFactors);
|
||||
|
||||
Map<String, String> reasonsForRefusal = new LinkedHashMap<>();
|
||||
reasonsForRefusal.put("high_price", "EXAMPLE__REFUSAL_HIGH_PRICE");
|
||||
reasonsForRefusal.put("no_reviews_social_proof", "EXAMPLE__REFUSAL_NO_REVIEWS");
|
||||
reasonsForRefusal.put("choice_difficulty_unclear_offer", "EXAMPLE__REFUSAL_UNCLEAR_OFFER");
|
||||
reasonsForRefusal.put("Скрытые условия / Нет цены", "40%");
|
||||
reasonsForRefusal.put("Плохие отзывы / Нет кейсов", "30%");
|
||||
reasonsForRefusal.put("Долгая реакция менеджера", "30%");
|
||||
demandAndBehavior.setReasonsForRefusal(reasonsForRefusal);
|
||||
demandAndBehavior.setConclusion("EXAMPLE__DEMAND_AND_BEHAVIOR_CONCLUSION");
|
||||
|
||||
demandAndBehavior.setConclusion("Покупатель стал избирательным: он читает отзывы, сравнивает цены и ищет социальные доказательства. Критически важна прозрачность условий и скорость ответа.");
|
||||
sections.setDemandAndBehavior(demandAndBehavior);
|
||||
|
||||
// 5. Customer Journey
|
||||
MarketingAnalysisResponseV2.CustomerJourney customerJourney = new MarketingAnalysisResponseV2.CustomerJourney();
|
||||
customerJourney.setTitle("EXAMPLE__CUSTOMER_JOURNEY_TITLE");
|
||||
customerJourney.setTitle("Воронка продаж");
|
||||
|
||||
MarketingAnalysisResponseV2.FunnelMetrics funnelMetrics = new MarketingAnalysisResponseV2.FunnelMetrics();
|
||||
funnelMetrics.setAdTraffic("EXAMPLE__FUNNEL_AD_TRAFFIC");
|
||||
funnelMetrics.setSiteVisitors("EXAMPLE__FUNNEL_SITE_VISITORS");
|
||||
funnelMetrics.setLeads(-9999);
|
||||
funnelMetrics.setBookings(-9999);
|
||||
funnelMetrics.setPayments(-9999);
|
||||
funnelMetrics.setAdTraffic("~15 000 показов");
|
||||
funnelMetrics.setSiteVisitors("~800 переходов (CTR 1.5%)"); // Средний CTR
|
||||
funnelMetrics.setLeads(150); // Конверсия лендинга ~3-5% (Реалистично для B2C)
|
||||
funnelMetrics.setBookings(60); // Квал. лиды (40% от заявок)
|
||||
funnelMetrics.setPayments(45); // Продажи (Close Rate 30% от квал. лидов)
|
||||
customerJourney.setFunnelMetrics(funnelMetrics);
|
||||
|
||||
MarketingAnalysisResponseV2.ConversionRates conversionRates = new MarketingAnalysisResponseV2.ConversionRates();
|
||||
conversionRates.setLeadToBooking("EXAMPLE__LEAD_TO_BOOKING");
|
||||
conversionRates.setBookingToPayment("EXAMPLE__BOOKING_TO_PAYMENT");
|
||||
conversionRates.setLeadToPaymentTotal("EXAMPLE__LEAD_TO_PAYMENT_TOTAL");
|
||||
conversionRates.setLeadToBooking("40%"); // Квалификация
|
||||
conversionRates.setBookingToPayment("75%"); // Дожим
|
||||
conversionRates.setLeadToPaymentTotal("30%"); // Общая конверсия в продажу
|
||||
customerJourney.setConversionRates(conversionRates);
|
||||
|
||||
MarketingAnalysisResponseV2.FunnelBottlenecks funnelBottlenecks = new MarketingAnalysisResponseV2.FunnelBottlenecks();
|
||||
funnelBottlenecks.setLeadToBooking("EXAMPLE__BOTTLENECK_LEAD_TO_BOOKING");
|
||||
funnelBottlenecks.setBookingToPayment("EXAMPLE__BOTTLENECK_BOOKING_TO_PAYMENT");
|
||||
funnelBottlenecks.setLeadToBooking("Недозвон / Долгая реакция на заявку");
|
||||
funnelBottlenecks.setBookingToPayment("Возражение 'Дорого' / Отсутствие рассрочки");
|
||||
customerJourney.setFunnelBottlenecks(funnelBottlenecks);
|
||||
customerJourney.setConclusion("EXAMPLE__CUSTOMER_JOURNEY_CONCLUSION");
|
||||
|
||||
customerJourney.setConclusion("Узкое место — обработка входящих заявок. Потеря 60% лидов на этапе квалификации говорит о проблемах в скриптах или скорости реакции.");
|
||||
sections.setCustomerJourney(customerJourney);
|
||||
|
||||
// acquisition_channels
|
||||
// 6. Acquisition Channels
|
||||
MarketingAnalysisResponseV2.AcquisitionChannels acquisitionChannels = new MarketingAnalysisResponseV2.AcquisitionChannels();
|
||||
acquisitionChannels.setTitle("EXAMPLE__ACQUISITION_CHANNELS_TITLE");
|
||||
acquisitionChannels.setTitle("Каналы трафика");
|
||||
|
||||
MarketingAnalysisResponseV2.AcquisitionChannelsMetrics acMetrics = new MarketingAnalysisResponseV2.AcquisitionChannelsMetrics();
|
||||
acMetrics.setSearchTrafficShare("EXAMPLE__SEARCH_TRAFFIC_SHARE");
|
||||
acMetrics.setAverageLeadCost("EXAMPLE__AVERAGE_LEAD_COST");
|
||||
acMetrics.setGrowthPotential("EXAMPLE__GROWTH_POTENTIAL");
|
||||
acMetrics.setSearchTrafficShare("~30% (SEO + Context)");
|
||||
acMetrics.setAverageLeadCost("2500 - 4500 KZT"); // Реальный CPL для услуг
|
||||
acMetrics.setGrowthPotential("TikTok / Reels (Short Video)");
|
||||
acquisitionChannels.setMetrics(acMetrics);
|
||||
|
||||
Map<String, String> channelShares = new LinkedHashMap<>();
|
||||
channelShares.put("instagram_target", "EXAMPLE__SHARE_INSTA");
|
||||
channelShares.put("tiktok_ads", "EXAMPLE__SHARE_TIKTOK");
|
||||
channelShares.put("kaspi_marketing", "EXAMPLE__SHARE_KASPI");
|
||||
channelShares.put("google_ads", "EXAMPLE__SHARE_GOOGLE");
|
||||
channelShares.put("2gis_ads", "EXAMPLE__SHARE_2GIS");
|
||||
acquisitionChannels.setChannelShares(channelShares);
|
||||
channelShares.put("Instagram (Target + Content)", "50%");
|
||||
channelShares.put("Google Ads (Search)", "20%");
|
||||
channelShares.put("TikTok (Ads + Organic)", "15%");
|
||||
channelShares.put("2GIS / Maps", "10%");
|
||||
channelShares.put("Other (Referral)", "5%");
|
||||
acquisitionChannels.setChannelShares(channelShares);
|
||||
|
||||
Map<String, MarketingAnalysisResponseV2.ChannelEfficiency> channelEfficiency = new LinkedHashMap<>();
|
||||
channelEfficiency.put("instagram",
|
||||
new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_INSTA", "EXAMPLE__EFF_HIGH"));
|
||||
new MarketingAnalysisResponseV2.ChannelEfficiency("2000-3500 ₸", "High (Масштаб)"));
|
||||
channelEfficiency.put("tiktok",
|
||||
new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_TIKTOK", "EXAMPLE__EFF_HIGH"));
|
||||
new MarketingAnalysisResponseV2.ChannelEfficiency("1500-2500 ₸", "High (Дешево)"));
|
||||
channelEfficiency.put("kaspi",
|
||||
new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_KASPI", "EXAMPLE__EFF_VERY_HIGH"));
|
||||
new MarketingAnalysisResponseV2.ChannelEfficiency("5% CPA", "Very High (Горячие)"));
|
||||
channelEfficiency.put("google_search",
|
||||
new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_GOOGLE", "EXAMPLE__EFF_MEDIUM"));
|
||||
new MarketingAnalysisResponseV2.ChannelEfficiency("4000-6000 ₸", "Medium (Дорого)"));
|
||||
channelEfficiency.put("2gis",
|
||||
new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_2GIS", "EXAMPLE__EFF_LOCAL"));
|
||||
channelEfficiency.put("threads_meta",
|
||||
new MarketingAnalysisResponseV2.ChannelEfficiency("EXAMPLE__CPL_THREADS", "EXAMPLE__EFF_NEW"));
|
||||
new MarketingAnalysisResponseV2.ChannelEfficiency("Бесплатно", "Local (Гео)"));
|
||||
acquisitionChannels.setChannelEfficiency(channelEfficiency);
|
||||
acquisitionChannels.setConclusion("EXAMPLE__ACQUISITION_CHANNELS_CONCLUSION");
|
||||
|
||||
acquisitionChannels.setConclusion("Instagram остается основным драйвером продаж. Google Ads эффективен, но дорог. Рекомендуется диверсификация через TikTok для снижения стоимости лида.");
|
||||
sections.setAcquisitionChannels(acquisitionChannels);
|
||||
|
||||
// marketing_economics
|
||||
// 7. Marketing Economics
|
||||
MarketingAnalysisResponseV2.MarketingEconomics marketingEconomics = new MarketingAnalysisResponseV2.MarketingEconomics();
|
||||
marketingEconomics.setTitle("EXAMPLE__MARKETING_ECONOMICS_TITLE");
|
||||
marketingEconomics.setTitle("Юнит-экономика");
|
||||
|
||||
MarketingAnalysisResponseV2.MarketingEconomicsMetrics meMetrics = new MarketingAnalysisResponseV2.MarketingEconomicsMetrics();
|
||||
meMetrics.setCustomerAcquisitionCostCac("EXAMPLE__CAC");
|
||||
meMetrics.setRepeatSalesConversion("EXAMPLE__REPEAT_SALES_CONVERSION");
|
||||
meMetrics.setRoi("EXAMPLE__ROI");
|
||||
meMetrics.setCustomerAcquisitionCostCac("3 500 KZT"); // Целевой CAC
|
||||
meMetrics.setRepeatSalesConversion("25-30%"); // LTV потенциал
|
||||
meMetrics.setRoi("250%"); // Здоровый ROI (3.5x ROAS)
|
||||
marketingEconomics.setMetrics(meMetrics);
|
||||
|
||||
MarketingAnalysisResponseV2.FinancialPerformance financialPerformance = new MarketingAnalysisResponseV2.FinancialPerformance();
|
||||
financialPerformance
|
||||
.setMarketingExpenses(
|
||||
new MarketingAnalysisResponseV2.MonthYearValue("EXAMPLE__MARKETING_EXPENSES_MONTH",
|
||||
"EXAMPLE__MARKETING_EXPENSES_YEAR"));
|
||||
financialPerformance.setClients(new MarketingAnalysisResponseV2.MonthYearValue("EXAMPLE__CLIENTS_MONTH",
|
||||
"EXAMPLE__CLIENTS_YEAR"));
|
||||
financialPerformance
|
||||
.setCac(new MarketingAnalysisResponseV2.MonthYearValue("EXAMPLE__CAC_MONTH", "EXAMPLE__CAC_YEAR"));
|
||||
financialPerformance
|
||||
.setRevenueFromClient(new MarketingAnalysisResponseV2.MonthYearValue("EXAMPLE__REV_PER_CLIENT_MONTH",
|
||||
"EXAMPLE__REV_PER_CLIENT_YEAR"));
|
||||
financialPerformance
|
||||
.setProfitFromClient(new MarketingAnalysisResponseV2.MonthYearValue("EXAMPLE__PROFIT_PER_CLIENT_MONTH",
|
||||
"EXAMPLE__PROFIT_PER_CLIENT_YEAR"));
|
||||
// Бюджет 1.5 млн (Адекватная цифра для малого/среднего бизнеса)
|
||||
financialPerformance.setMarketingExpenses(
|
||||
new MarketingAnalysisResponseV2.MonthYearValue("1.5 млн KZT", "18 млн KZT"));
|
||||
|
||||
// Клиенты: 1.5млн / 3500 = ~428 клиентов
|
||||
financialPerformance.setClients(
|
||||
new MarketingAnalysisResponseV2.MonthYearValue("~430", "~5 160"));
|
||||
|
||||
financialPerformance.setCac(
|
||||
new MarketingAnalysisResponseV2.MonthYearValue("3 500 KZT", "3 200 KZT (Оптимизация)"));
|
||||
|
||||
// Выручка (Чек 25 000 * 430 = 10.7 млн)
|
||||
financialPerformance.setRevenueFromClient(
|
||||
new MarketingAnalysisResponseV2.MonthYearValue("25 000 KZT (Чек)", "300 000 KZT (LTV)"));
|
||||
|
||||
// Прибыль (Маржа 30%)
|
||||
financialPerformance.setProfitFromClient(
|
||||
new MarketingAnalysisResponseV2.MonthYearValue("7 500 KZT", "90 000 KZT"));
|
||||
|
||||
marketingEconomics.setFinancialPerformance(financialPerformance);
|
||||
marketingEconomics.setConclusion("EXAMPLE__MARKETING_ECONOMICS_CONCLUSION");
|
||||
marketingEconomics.setConclusion("При текущем CAC модель масштабируема. Основной рычаг роста прибыли — увеличение LTV (повторных покупок) и среднего чека.");
|
||||
sections.setMarketingEconomics(marketingEconomics);
|
||||
|
||||
// bottlenecks
|
||||
// 8. Bottlenecks
|
||||
MarketingAnalysisResponseV2.Bottlenecks bottlenecks = new MarketingAnalysisResponseV2.Bottlenecks();
|
||||
bottlenecks.setTitle("EXAMPLE__BOTTLENECKS_TITLE");
|
||||
bottlenecks.setTitle("Точки роста и проблемы");
|
||||
|
||||
Map<String, String> statistics = new LinkedHashMap<>();
|
||||
statistics.put("landing_page_issues", "EXAMPLE__STAT_LANDING_PAGE_ISSUES");
|
||||
statistics.put("slow_manager_responses", "EXAMPLE__STAT_SLOW_MANAGER_RESPONSES");
|
||||
statistics.put("repeat_interaction_refusal", "EXAMPLE__STAT_REPEAT_INTERACTION_REFUSAL");
|
||||
statistics.put("landing_page_issues", "Высокий показатель отказов (60%)");
|
||||
statistics.put("slow_manager_responses", "Звонок > 15 мин (30% потерь)");
|
||||
statistics.put("repeat_interaction_refusal", "Нет дожима в CRM (20% потерь)");
|
||||
bottlenecks.setStatistics(statistics);
|
||||
|
||||
MarketingAnalysisResponseV2.BottlenecksDetails details = new MarketingAnalysisResponseV2.BottlenecksDetails();
|
||||
details.setLanding(List.of("EXAMPLE__LANDING_ISSUE_1", "EXAMPLE__LANDING_ISSUE_2", "EXAMPLE__LANDING_ISSUE_3"));
|
||||
details.setLeadManagement(
|
||||
List.of("EXAMPLE__LEAD_MGMT_ISSUE_1", "EXAMPLE__LEAD_MGMT_ISSUE_2", "EXAMPLE__LEAD_MGMT_ISSUE_3"));
|
||||
details.setRetention(
|
||||
List.of("EXAMPLE__RETENTION_ISSUE_1", "EXAMPLE__RETENTION_ISSUE_2", "EXAMPLE__RETENTION_ISSUE_3"));
|
||||
details.setLanding(List.of("Долгая загрузка на мобильных", "Непонятный первый экран (УТП)", "Сложная форма заявки"));
|
||||
details.setLeadManagement(List.of("Менеджеры не перезванивают", "Нет скриптов продаж", "Нет интеграции с WhatsApp"));
|
||||
details.setRetention(List.of("Нет программы лояльности", "Нет сбора обратной связи (NPS)", "Нет Email-маркетинга"));
|
||||
bottlenecks.setDetails(details);
|
||||
bottlenecks.setConclusion("EXAMPLE__BOTTLENECKS_CONCLUSION");
|
||||
|
||||
bottlenecks.setConclusion("Технические ошибки сайта и человеческий фактор в продажах — главные ограничители роста. Их устранение может удвоить конверсию без увеличения бюджета.");
|
||||
sections.setBottlenecks(bottlenecks);
|
||||
|
||||
// recommendations
|
||||
// 9. Recommendations
|
||||
MarketingAnalysisResponseV2.Recommendations recommendations = new MarketingAnalysisResponseV2.Recommendations();
|
||||
recommendations.setTitle("EXAMPLE__RECOMMENDATIONS_TITLE");
|
||||
recommendations.setTitle("План действий");
|
||||
recommendations.setCorePillars(List.of(
|
||||
new MarketingAnalysisResponseV2.CorePillar("EXAMPLE__PILLAR_1_TITLE", "EXAMPLE__PILLAR_1_DESC"),
|
||||
new MarketingAnalysisResponseV2.CorePillar("EXAMPLE__PILLAR_2_TITLE", "EXAMPLE__PILLAR_2_DESC"),
|
||||
new MarketingAnalysisResponseV2.CorePillar("EXAMPLE__PILLAR_3_TITLE", "EXAMPLE__PILLAR_3_DESC"),
|
||||
new MarketingAnalysisResponseV2.CorePillar("EXAMPLE__PILLAR_4_TITLE", "EXAMPLE__PILLAR_4_DESC")));
|
||||
recommendations.setConclusion("EXAMPLE__RECOMMENDATIONS_CONCLUSION");
|
||||
new MarketingAnalysisResponseV2.CorePillar("Быстрые победы", "Запустить ретаргетинг на посетителей сайта и внедрить авто-ответ в WhatsApp."),
|
||||
new MarketingAnalysisResponseV2.CorePillar("Оптимизация сайта", "Упростить форму заявки до 2 полей (Имя, Телефон) и ускорить мобильную версию."),
|
||||
new MarketingAnalysisResponseV2.CorePillar("Контент-маркетинг", "Снять 10 Reels с отзывами клиентов и процессом работы для повышения доверия."),
|
||||
new MarketingAnalysisResponseV2.CorePillar("Систематизация", "Внедрить CRM для контроля пропущенных звонков и задач менеджеров.")
|
||||
));
|
||||
recommendations.setConclusion("Фокус на ближайший квартал: повышение конверсии сайта и автоматизация первичной обработки лидов.");
|
||||
sections.setRecommendations(recommendations);
|
||||
|
||||
response.setSections(sections);
|
||||
|
||||
Reference in New Issue
Block a user