fix
This commit is contained in:
@@ -639,53 +639,38 @@ public class MarketingAnalysisService {
|
|||||||
request.getBusinessNiche(), request.getRegion());
|
request.getBusinessNiche(), request.getRegion());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// -------------------------------------------------------
|
// 1. Сбор данных (оставляем как есть, это работает нормально)
|
||||||
// 1. Сбор данных (Research Phase)
|
|
||||||
// -------------------------------------------------------
|
|
||||||
// Собираем данные из Google/SerpApi, чтобы у GPT был контекст (реальные сайты, статьи)
|
|
||||||
Map<String, Object> researchPack = buildResearchPack(request);
|
Map<String, Object> researchPack = buildResearchPack(request);
|
||||||
|
|
||||||
// Пытаемся найти 3-5 реальных конкурентов через поиск для подстановки в промпт
|
// Получаем список конкурентов для контекста
|
||||||
List<String> competitorCompanies = resolveCompetitorCompaniesForBenchmarks(request, researchPack, 5);
|
List<String> competitorCompanies = resolveCompetitorCompaniesForBenchmarks(request, researchPack, 5);
|
||||||
researchPack.put("competitor_companies", competitorCompanies);
|
|
||||||
|
|
||||||
// -------------------------------------------------------
|
// 2. Формирование Промпта
|
||||||
// 2. Формирование "Идеального примера" (Structure & Benchmarks)
|
// ВАЖНО: Мы больше не передаем заполненный пример (exampleDto).
|
||||||
// -------------------------------------------------------
|
// Мы передаем только структуру (Schema) и жесткие инструкции.
|
||||||
// Мы создаем Java-объект с "умными" рыбами (бенчмарками), который GPT будет использовать как шаблон.
|
|
||||||
// Это гарантирует, что структура JSON будет 1-в-1 как ждет фронтенд.
|
|
||||||
MarketingAnalysisResponseV2 exampleDto = buildAnalysisV2ExampleForPrompt(request, competitorCompanies);
|
|
||||||
|
|
||||||
// Сериализуем пример в строку, чтобы вставить в промпт
|
|
||||||
String structureExampleJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(exampleDto);
|
|
||||||
|
|
||||||
// -------------------------------------------------------
|
|
||||||
// 3. Формирование Промпта (System & User Instructions)
|
|
||||||
// -------------------------------------------------------
|
|
||||||
String systemPrompt = buildSystemPromptForKazakhstan();
|
String systemPrompt = buildSystemPromptForKazakhstan();
|
||||||
String instruction = buildDetailedInstruction(request, structureExampleJson);
|
String instruction = buildDetailedInstruction(request, competitorCompanies);
|
||||||
|
|
||||||
// Собираем весь контекст для отправки
|
// Собираем контекст (запрос + найденные данные)
|
||||||
String contextJson = objectMapper.writeValueAsString(Map.of(
|
String contextJson = objectMapper.writeValueAsString(Map.of(
|
||||||
"user_request", request,
|
"user_input", request,
|
||||||
"market_evidence_from_search", researchPack
|
"search_data", researchPack, // Данные из Google/SerpApi
|
||||||
|
"identified_competitors", competitorCompanies
|
||||||
));
|
));
|
||||||
|
|
||||||
// -------------------------------------------------------
|
// 3. Вызов OpenAI
|
||||||
// 4. Вызов AI с механизмом Retry (на случай битого JSON)
|
|
||||||
// -------------------------------------------------------
|
|
||||||
for (int attempt = 1; attempt <= 2; attempt++) {
|
for (int attempt = 1; attempt <= 2; attempt++) {
|
||||||
try {
|
try {
|
||||||
logger.info("Sending request to OpenAI (Attempt {}/2)...", attempt);
|
// Используем модель с большим контекстом (gpt-4o или gpt-4-turbo)
|
||||||
|
|
||||||
String aiResponse = openAIAnalyticsService.generateWithInstructionWithModel(
|
String aiResponse = openAIAnalyticsService.generateWithInstructionWithModel(
|
||||||
contextJson,
|
contextJson,
|
||||||
instruction,
|
instruction,
|
||||||
"ru",
|
"ru",
|
||||||
textModelName, // GPT-4o или o1-preview
|
"gpt-4o", // Обязательно мощная модель для аналитики
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
16000, // Max tokens (увеличенный лимит для детального ответа)
|
16000,
|
||||||
180000L // Timeout (3 минуты)
|
180000L
|
||||||
);
|
);
|
||||||
|
|
||||||
if (aiResponse == null || aiResponse.isBlank()) {
|
if (aiResponse == null || aiResponse.isBlank()) {
|
||||||
@@ -693,120 +678,265 @@ public class MarketingAnalysisService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String cleaned = cleanJsonFromMarkdown(aiResponse);
|
String cleaned = cleanJsonFromMarkdown(aiResponse);
|
||||||
if (cleaned != null) {
|
|
||||||
cleaned = cleaned.replace("\uFEFF", "").trim();
|
|
||||||
}
|
|
||||||
MarketingAnalysisResponseV2 parsed = objectMapper.readValue(cleaned, MarketingAnalysisResponseV2.class);
|
MarketingAnalysisResponseV2 parsed = objectMapper.readValue(cleaned, MarketingAnalysisResponseV2.class);
|
||||||
replacePlaceholderCompanyKeysIfNeeded(parsed, competitorCompanies);
|
|
||||||
|
|
||||||
logger.info("<<< Analysis V2 generated successfully.");
|
// Финальная валидация (если вдруг вернул фитнес данные - можно тут добавить проверку)
|
||||||
return parsed;
|
return parsed;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("JSON Parsing/Generation failed on attempt {}: {}", attempt, e.getMessage());
|
logger.error("JSON Parsing/Generation failed on attempt {}: {}", attempt, e.getMessage());
|
||||||
if (attempt == 2) throw e; // Если второй раз не вышло — пробрасываем ошибку
|
if (attempt == 2) throw e;
|
||||||
Thread.sleep(2000); // Пауза перед повтором
|
Thread.sleep(2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("Failed to generate valid analysis after retries.");
|
throw new IllegalStateException("Failed to generate analysis.");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("generateAnalysisV2: Fatal error: {}", e.getMessage(), e);
|
logger.error("Fatal error: {}", e.getMessage(), e);
|
||||||
throw new RuntimeException("Не удалось сгенерировать маркетинговый анализ V2: " + e.getMessage(), e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildSystemPromptForKazakhstan() {
|
private String buildSystemPromptForKazakhstan() {
|
||||||
return KZ_MARKET_CONTEXT +
|
return """
|
||||||
"\nФОРМАТ ОТВЕТА:\n" +
|
РОЛЬ И МИССИЯ:
|
||||||
"- ТОЛЬКО валидный JSON.\n" +
|
Ты — Ведущий Партнер (Senior Partner) стратегического консалтинга уровня Big 4 (McKinsey, BCG, PwC), специализирующийся на рынке Центральной Азии и Казахстана.
|
||||||
"- Никакого Markdown форматирования (```json ... ```) вокруг ответа.\n" +
|
Твой клиент — требовательный инвестор, который вкладывает свои деньги. Твоя задача — дать ему сухую, аналитическую, ПРАВДИВУЮ оценку рынка, а не "воду" или выдумки.
|
||||||
"- Ключи JSON должны быть в snake_case (например, market_overview, monthly_online_index).\n" +
|
|
||||||
"- Запрещено возвращать null в полях (особенно income_by_age) — делай экстраполяцию если нет точных данных.\n";
|
ТЫ МЫСЛИШЬ В ВАЛЮТЕ: KZT (Казахстанский Тенге).
|
||||||
|
|
||||||
|
=============== 1. БАЗА ЗНАНИЙ О РЫНКЕ КАЗАХСТАНА (КОНТЕКСТ) ===============
|
||||||
|
Ты обязан использовать эти факты при генерации стратегии:
|
||||||
|
|
||||||
|
1. **Экосистема Kaspi.kz — это "воздух" для бизнеса:**
|
||||||
|
* Kaspi Pay/QR — стандарт де-факто для приема оплат (даже на базаре).
|
||||||
|
* Kaspi Магазин — главный маркетплейс для электроники, дома, одежды.
|
||||||
|
* Kaspi Red/Kredit — драйвер продаж. Люди покупают в рассрочку даже кроссовки и еду. Если чек > 10 000 KZT, рассрочка обязательна.
|
||||||
|
|
||||||
|
2. **Поисковое поведение:**
|
||||||
|
* Google — для "горячего" спроса и B2B услуг ("купить бетон", "юрист Алматы").
|
||||||
|
* 2GIS — ГЛАВНЫЙ поисковик для офлайн-точек (рестораны, СТО, салоны). Отзывы там важнее, чем на сайте.
|
||||||
|
* Instagram — это "второй Google". Люди ищут товары и услуги через поиск в Инсте. Визуал критичен.
|
||||||
|
* TikTok — главный источник дешёвого охвата и виральности, но конверсия в продажу холоднее.
|
||||||
|
|
||||||
|
3. **Региональная специфика (География):**
|
||||||
|
* *Алматы:* Высокая платежеспособность, креативный класс, высокая конкуренция, пробки (важна доставка).
|
||||||
|
* *Астана:* Госсектор, клерки, холодно (сезонность!), высокий чек, но население меньше Алматы.
|
||||||
|
* *Шымкент/Юг:* Традиционный уклад, важны семейные связи, "сарафанное радио", торговля, гостеприимство. Высокая чувствительность к цене.
|
||||||
|
* *Запад (Атырау, Актау):* Нефтяной сектор, высокие зарплаты, но дорогая логистика.
|
||||||
|
|
||||||
|
4. **Экономика:**
|
||||||
|
* Реальная инфляция потребительской корзины: ~10-15%.
|
||||||
|
* Средняя ЗП: официально ~350-400к, медианная ~250к. Учитывай это при оценке емкости рынка!
|
||||||
|
* Язык: Двуязычие. Реклама на казахском языке растет в эффективности (особенно на Юге и Западе).
|
||||||
|
|
||||||
|
=============== 2. ПРОТОКОЛ ЗАЩИТЫ ОТ ШАБЛОНОВ (ANTI-HALLUCINATION) ===============
|
||||||
|
СТРОГО ЗАПРЕЩЕНО:
|
||||||
|
1. Использовать кейс "Фитнес" (1Fit, Sport Zal), если пользователь спрашивает про другую нишу.
|
||||||
|
2. Писать "+18.5% YoY", если у тебя нет данных. Генерируй реалистичный тренд для КОНКРЕТНОЙ ниши (для хлеба это +2%, для IT +25%).
|
||||||
|
3. Выдумывать несуществующие законы РК.
|
||||||
|
4. Оставлять поля `null`. Если данных нет — используй метод Ферми (логическую оценку) и пиши "Оценочно ~X".
|
||||||
|
|
||||||
|
=============== 3. АНАЛИТИЧЕСКИЙ ФРЕЙМВОРК (КАК ДУМАТЬ) ===============
|
||||||
|
Перед генерацией JSON, прогони запрос через этот алгоритм:
|
||||||
|
|
||||||
|
ШАГ 1: Пойми юнит-экономику ниши.
|
||||||
|
- Если это "Недвижимость": LTV = 1-2 сделки, CAC = 100-500$+, цикл = месяцы.
|
||||||
|
- Если это "Доставка пиццы": LTV = высокий (годами), CAC = дешевый, цикл = минуты.
|
||||||
|
- Если это "Одежда": Важна сезонность, возвраты, визуальный контент.
|
||||||
|
|
||||||
|
ШАГ 2: Определи конкурентов.
|
||||||
|
- Не пиши "Сетевые лидеры". Назови реальные бренды в KZ, если знаешь (Sulpak, Technodom для техники; Magnum для еды). Если не знаешь — пиши "Локальные игроки малого бизнеса".
|
||||||
|
|
||||||
|
ШАГ 3: Рассчитай метрики.
|
||||||
|
- ROI 250% возможен не везде. В стройке маржа меньше, в инфобизнесе больше. Адаптируй цифры!
|
||||||
|
|
||||||
|
=============== 4. ТЕХНИЧЕСКИЙ ФОРМАТ ОТВЕТА ===============
|
||||||
|
1. Выдай ТОЛЬКО чистый JSON. Без ```json ... ```.
|
||||||
|
2. Все ключи в `snake_case`.
|
||||||
|
3. Поле `income_by_age` должно содержать ЧИСЛА (Integer), а не строки.
|
||||||
|
4. Структура JSON должна строго соответствовать запрошенному примеру (но с ТВОИМИ данными).
|
||||||
|
|
||||||
|
Твоя репутация зависит от точности цифр и глубины инсайтов. Действуй.
|
||||||
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildDetailedInstruction(MarketingAnalysisRequest request, String structureExampleJson) {
|
private String buildDetailedInstruction(MarketingAnalysisRequest request, List<String> competitors) {
|
||||||
String regionsList;
|
String niche = request.getBusinessNiche();
|
||||||
if (request.getRegion() != null && !request.getRegion().isEmpty()) {
|
String region = (request.getRegion() != null && !request.getRegion().isEmpty())
|
||||||
regionsList = String.join(", ", request.getRegion());
|
? String.join(", ", request.getRegion())
|
||||||
} else {
|
: "Казахстан";
|
||||||
regionsList = "Алматы, Астана, Шымкент, Караганда";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Логика возрастных групп
|
return """
|
||||||
String ageRangesInstruction = "";
|
Твоя задача — провести глубокий маркетинговый анализ для ниши: "%s" в регионе: "%s".
|
||||||
if (request.getTargetAudience() != null && request.getTargetAudience().containsKey("ageRanges")) {
|
|
||||||
@SuppressWarnings("unchecked")
|
ТЫ ОБЯЗАН ИСПОЛЬЗОВАТЬ ПРЕДОСТАВЛЕННЫЕ ДАННЫЕ ИЗ 'search_data'.
|
||||||
List<String> ranges = (List<String>) request.getTargetAudience().get("ageRanges");
|
ЕСЛИ ДАННЫХ НЕТ — ИСПОЛЬЗУЙ ЛОГИКУ И ОБЩИЕ ЭКОНОМИЧЕСКИЕ ПОКАЗАТЕЛИ КАЗАХСТАНА (Инфляция, ВВП, Сезонность этой ниши).
|
||||||
if (ranges != null && !ranges.isEmpty()) {
|
|
||||||
ageRangesInstruction = "В полях 'age_structure' и 'income_by_age' используй СТРОГО только эти возрастные группы: " + String.join(", ", ranges) + ".";
|
ЗАПРЕТ:
|
||||||
|
1. ЗАПРЕЩЕНО использовать примеры из фитнеса, если ниша не фитнес.
|
||||||
|
2. ЗАПРЕЩЕНО писать "+18.5%% YoY", если это не подтверждено данными. Генерируй реалистичные цифры для ЭТОЙ ниши.
|
||||||
|
3. Не возвращай Markdown. Только чистый JSON.
|
||||||
|
|
||||||
|
СТРУКТУРА ОТВЕТА (строго следуй этому формату JSON, поля должны быть заполнены аналитикой):
|
||||||
|
|
||||||
|
{
|
||||||
|
"report_title": "Стратегический анализ рынка: [Название Ниши]",
|
||||||
|
"sections": {
|
||||||
|
"market_overview": {
|
||||||
|
"title": "Обзор рынка",
|
||||||
|
"metrics": {
|
||||||
|
"online_demand_index": "Оцени спрос от 1 до 10 (строка, например 'Высокий (8/10)') на основе популярности ниши.",
|
||||||
|
"online_potential": "Трендовое описание (например, 'Стабильный рост' или 'Сезонный спад').",
|
||||||
|
"demand_growth_rate_yoy": "Процент роста за год (реалистичный для %s, например +5%% для хлеба, +30%% для AI).",
|
||||||
|
"seasonality_coefficient_spring": "Коэффициент (например, 1.2). Учитывай, когда сезон у %s.",
|
||||||
|
"seasonality_range": "Месяцы высокого сезона (например, 'Май - Август' для кондиционеров)."
|
||||||
|
},
|
||||||
|
"monthly_online_index": {
|
||||||
|
// Заполни Double значениями от 0.5 до 1.5 для каждого месяца (january...december).
|
||||||
|
// ВАЖНО: Эти цифры должны отражать РЕАЛЬНУЮ сезонность ниши "%s".
|
||||||
|
},
|
||||||
|
"geography": {
|
||||||
|
// Map<String, Integer>. Ключ - город Казахстана, Значение - индекс интереса (0-100).
|
||||||
|
// Учитывай регион "%s". Если регион вся страна - укажи топ городов.
|
||||||
|
},
|
||||||
|
"conclusion": "Аналитический вывод о состоянии рынка (2-3 предложения)."
|
||||||
|
},
|
||||||
|
"target_audience": {
|
||||||
|
"title": "Портрет целевой аудитории",
|
||||||
|
"metrics": {
|
||||||
|
"total_audience_size": "Оценка кол-ва потенциальных клиентов (число).",
|
||||||
|
"leading_segment_share": "Кто основной покупатель? (Пол/Возраст).",
|
||||||
|
"above_average_income_share": "Доля аудитории с доходом выше среднего (%%)."
|
||||||
|
},
|
||||||
|
"income_by_age": {
|
||||||
|
// Map<String, Integer>. Средний доход в KZT для групп: '18-24', '25-34', '35-44', '45+'.
|
||||||
|
// Используй реальные данные по зарплатам в Казахстане.
|
||||||
|
},
|
||||||
|
"segments": [
|
||||||
|
// List of objects {name, share}. Сегментируй аудиторию этой ниши (например 'Новички', 'Профи' или 'B2B', 'B2C').
|
||||||
|
],
|
||||||
|
"age_structure": {
|
||||||
|
// Map<String, String>. Распределение по возрастам в процентах.
|
||||||
|
},
|
||||||
|
"conclusion": "Вывод о портрете клиента."
|
||||||
|
},
|
||||||
|
"competitive_environment": {
|
||||||
|
"title": "Конкурентный ландшафт",
|
||||||
|
"metrics": {
|
||||||
|
"leader_market_share": "Оценка доли лидера рынка.",
|
||||||
|
"competition_level": "Уровень конкуренции (Низкая/Средняя/Высокая/Красный океан).",
|
||||||
|
"ad_pressure": "Давление рекламы (CPC, активность).",
|
||||||
|
"market_leader_name": "Название типа лидеров (например, 'Маркетплейсы' или 'Сетевые игроки')."
|
||||||
|
},
|
||||||
|
"competitors": [
|
||||||
|
// Используй список конкурентов из context: %s.
|
||||||
|
// Для каждого создай объект:
|
||||||
|
// {
|
||||||
|
// "name": "Имя",
|
||||||
|
// "website": "Сайт (если есть) или заглушка",
|
||||||
|
// "visibility_score": Число 0-100,
|
||||||
|
// "key_strengths": ["Сила 1", "Сила 2"],
|
||||||
|
// "price_segment": "Economy/Middle/Premium"
|
||||||
|
// }
|
||||||
|
],
|
||||||
|
"conclusion": "Вывод о конкуренции."
|
||||||
|
},
|
||||||
|
"demand_and_behavior": {
|
||||||
|
"title": "Спрос и поведение",
|
||||||
|
"metrics": {
|
||||||
|
"search_volume_growth": "Рост поисковых запросов.",
|
||||||
|
"interactions_before_purchase": "Сколько касаний нужно для покупки в нише %s (например, недвижимость - 20, еда - 1).",
|
||||||
|
"repeat_refusal_probability": "Вероятность отказа."
|
||||||
|
},
|
||||||
|
"search_query_types": {
|
||||||
|
// Типы запросов (ключ) и их доля (значение). Например: 'Информационные', 'Транзакционные'.
|
||||||
|
},
|
||||||
|
"decision_factors": {
|
||||||
|
// Факторы принятия решения (Цена, Качество, Отзывы) и их вес.
|
||||||
|
},
|
||||||
|
"reasons_for_refusal": {
|
||||||
|
// Почему не покупают? (Дорого, Долгая доставка и т.д.).
|
||||||
|
},
|
||||||
|
"conclusion": "Вывод о поведении."
|
||||||
|
},
|
||||||
|
"customer_journey": {
|
||||||
|
"title": "Путь клиента",
|
||||||
|
"funnel_metrics": {
|
||||||
|
"ad_traffic": "Примерный трафик (строка)",
|
||||||
|
"site_visitors": "Посетители",
|
||||||
|
"leads": "Лиды (число)",
|
||||||
|
"bookings": "Заказы/Записи",
|
||||||
|
"payments": "Оплаты"
|
||||||
|
// Рассчитай воронку с реалистичной конверсией для ниши %s.
|
||||||
|
},
|
||||||
|
"conversion_rates": {
|
||||||
|
"lead_to_booking": "%% конверсии",
|
||||||
|
"booking_to_payment": "%% конверсии",
|
||||||
|
"lead_to_payment_total": "Общая конверсия"
|
||||||
|
},
|
||||||
|
"funnel_bottlenecks": {
|
||||||
|
"lead_to_booking": "Главная проблема на этом этапе для ниши %s",
|
||||||
|
"booking_to_payment": "Главная проблема оплаты"
|
||||||
|
},
|
||||||
|
"conclusion": "Вывод по воронке."
|
||||||
|
},
|
||||||
|
"acquisition_channels": {
|
||||||
|
"title": "Каналы привлечения",
|
||||||
|
"metrics": {
|
||||||
|
"search_traffic_share": "%% поиска",
|
||||||
|
"average_lead_cost": "Стоимость лида в KZT (CPL). Реалистичная для ниши!",
|
||||||
|
"growth_potential": "Какой канал растет?"
|
||||||
|
},
|
||||||
|
"channel_shares": {
|
||||||
|
// Доли каналов (Instagram, Google, TikTok, Kaspi).
|
||||||
|
// ДЛЯ B2B - LinkedIn/Google. ДЛЯ B2C ТОВАРОВ - Kaspi/Instagram.
|
||||||
|
},
|
||||||
|
"channel_efficiency": {
|
||||||
|
// Map<String, {cost, status}>. Оцени эффективность каналов.
|
||||||
|
},
|
||||||
|
"conclusion": "Вывод по каналам."
|
||||||
|
},
|
||||||
|
"marketing_economics": {
|
||||||
|
"title": "Экономика маркетинга",
|
||||||
|
"metrics": {
|
||||||
|
"customer_acquisition_cost_cac": "CAC в KZT.",
|
||||||
|
"repeat_sales_conversion": "Retention rate %%.",
|
||||||
|
"roi": "Ожидаемый ROI %%."
|
||||||
|
},
|
||||||
|
"financial_performance": {
|
||||||
|
// Заполни объекты MonthYearValue ("month": "...", "year": "...")
|
||||||
|
// marketing_expenses, clients, cac, revenue_from_client, profit_from_client.
|
||||||
|
// Цифры должны биться с нишей (недвижимость - миллионы, носки - тысячи).
|
||||||
|
},
|
||||||
|
"conclusion": "Финансовый вывод."
|
||||||
|
},
|
||||||
|
"bottlenecks": {
|
||||||
|
"title": "Узкие места",
|
||||||
|
"statistics": {
|
||||||
|
// Статистика потерь (%%).
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"landing": ["Проблема 1", "Проблема 2"],
|
||||||
|
"lead_management": ["Проблема 1"],
|
||||||
|
"retention": ["Проблема 1"]
|
||||||
|
},
|
||||||
|
"conclusion": "Вывод по проблемам."
|
||||||
|
},
|
||||||
|
"recommendations": {
|
||||||
|
"title": "Рекомендации",
|
||||||
|
"core_pillars": [
|
||||||
|
// Список стратегий {strategy, action}.
|
||||||
|
// ДАЙ КОНКРЕТНЫЕ СОВЕТЫ ДЛЯ НИШИ %s (Например, 'Запустить Kaspi Ads' для товаров).
|
||||||
|
],
|
||||||
|
"conclusion": "Финальный совет."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
// Список URL из search_data
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
""".formatted(niche, region, niche, niche, region, niche, competitors != null ? competitors.toString() : "[]", niche, niche, niche, niche, niche);
|
||||||
String template = """
|
|
||||||
Твоя роль: Ты — Ведущий Маркетинговый Стратег (Senior Strategy Consultant) по рынку Казахстана.
|
|
||||||
Задача: Провести глубокий анализ для РЕАЛЬНОГО бизнеса (B2C/B2B). Никакой воды. Только факты, цифры и применимые советы.
|
|
||||||
|
|
||||||
ВХОДНЫЕ ДАННЫЕ:
|
|
||||||
Ниша: {{NICHE}}
|
|
||||||
Продукт: {{PRODUCT}}
|
|
||||||
Регионы для анализа: [{{REGIONS}}]
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
КРИТИЧЕСКИЕ ИНСТРУКЦИИ (KZ STANDARDS & NO NULLS):
|
|
||||||
|
|
||||||
0. **ЗАПРЕЩЕНО ВОЗВРАЩАТЬ NULL**:
|
|
||||||
- Каждая секция (`demand_and_behavior`, `bottlenecks`, `recommendations`) ДОЛЖНА быть заполнена.
|
|
||||||
- Если данных нет, используй экспертную оценку или бенчмарки по нише.
|
|
||||||
- НИКАКИХ ПУСТЫХ ПОЛЕЙ.
|
|
||||||
- ВСЕГДА ИСПОЛЬЗУЙ ПРАВИЛЬНЫЙ JSON, ПО ПЛАНКЕ КОТОРУЮ МЫ СКИНУЛ, И ЧТО БЫ ВСЕ ПРАВАИЛЬНО
|
|
||||||
|
|
||||||
1. **ГЕОГРАФИЯ (Market Overview):**
|
|
||||||
- Ты ОБЯЗАН включить в анализ (`geography`) ТОЛЬКО указанные города: [{{REGIONS}}].
|
|
||||||
- Если указан "Весь Казахстан" или список пуст — бери Топ-5 (Алматы, Астана, Шымкент...).
|
|
||||||
- Распределение: Алматы обычно лидер (40-50%), Астана (30%), регионы меньше.
|
|
||||||
|
|
||||||
2. **КОНКУРЕНТЫ (Competitive Environment):**
|
|
||||||
- Используй данные из `market_evidence_from_search`. Найди 3-5 РЕАЛЬНЫХ игроков.
|
|
||||||
- **ВАЖНО:** В поле `name` пиши ТОЛЬКО БРЕНД (например, "Fitnation", а не "ТОО Fitnation Group"). Без кавычек и юр. форм.
|
|
||||||
- В поле `website` попытайся найти реальный домен или ссылку на Instagram.
|
|
||||||
- Оценка видимости (`visibility_score`) должна быть разной (например: 95, 82, 60, 45). Не ставь всем 100.
|
|
||||||
|
|
||||||
3. **КАНАЛЫ (Acquisition Channels):**
|
|
||||||
- Приоритет в Казахстане (B2C): **Instagram (Reels/Stories), TikTok, Kaspi (магазин/платежи), 2GIS (карты)**.
|
|
||||||
- Google Ads — дорогой канал, ставь эффективность "Medium".
|
|
||||||
- `average_lead_cost`: Пиши текстом с валютой, диапазон (напр. "2500 - 4500 KZT").
|
|
||||||
|
|
||||||
4. **ЭКОНОМИКА (Marketing Economics):**
|
|
||||||
- **БЮДЖЕТ:** Рассчитывай модель для малого/среднего бизнеса (SME).
|
|
||||||
- Бюджет на маркетинг: **500 000 - 3 000 000 KZT** в месяц. НЕ БОЛЬШЕ.
|
|
||||||
- `cac` (Стоимость привлечения): Адекватная (2000 - 7000 KZT для масс-маркета услуг).
|
|
||||||
- `roi`: Реалистичный (150% - 300%). Не пиши фантастические 1000% на старте.
|
|
||||||
|
|
||||||
5. **ТИПЫ ДАННЫХ (Строго соблюдай типы):**
|
|
||||||
- `income_by_age`: Значения - ТОЛЬКО ЧИСЛА (Integer). Пример: 350000. (Никаких кавычек, запятых, "KZT").
|
|
||||||
- `monthly_online_index`: Дробные числа (0.0 - 1.0).
|
|
||||||
|
|
||||||
6. **ИСТОЧНИКИ:**
|
|
||||||
- Собери все ссылки из `evidence` и добавь авторитетные источники (stat.gov.kz, kase.kz, forbes.kz) в массив `sources` в самом конце.
|
|
||||||
|
|
||||||
================================================================================
|
|
||||||
{{AGE_INSTRUCTION}}
|
|
||||||
|
|
||||||
ФОРМАТ ОТВЕТА:
|
|
||||||
Верни СТРОГО валидный JSON, соответствующий структуре ниже.
|
|
||||||
Замени все значения `EXAMPLE__` и `-9999` на реальные аналитические данные, специфичные для ниши {{NICHE}}.
|
|
||||||
|
|
||||||
ЭТАЛОННАЯ СТРУКТУРА JSON (Следуй ей неукоснительно):
|
|
||||||
{{JSON_STRUCTURE}}
|
|
||||||
""";
|
|
||||||
|
|
||||||
return template
|
|
||||||
.replace("{{NICHE}}", request.getBusinessNiche())
|
|
||||||
.replace("{{PRODUCT}}", request.getProduct())
|
|
||||||
.replace("{{REGIONS}}", regionsList)
|
|
||||||
.replace("{{AGE_INSTRUCTION}}", ageRangesInstruction)
|
|
||||||
.replace("{{JSON_STRUCTURE}}", structureExampleJson);
|
|
||||||
}
|
}
|
||||||
private String buildJsonStructureExample() {
|
private String buildJsonStructureExample() {
|
||||||
return """
|
return """
|
||||||
@@ -1004,7 +1134,7 @@ public class MarketingAnalysisService {
|
|||||||
private List<String> resolveCompetitorCompaniesForBenchmarks(MarketingAnalysisRequest request,
|
private List<String> resolveCompetitorCompaniesForBenchmarks(MarketingAnalysisRequest request,
|
||||||
Map<String, Object> researchPack,
|
Map<String, Object> researchPack,
|
||||||
int limit) {
|
int limit) {
|
||||||
int max = Math.max(3, Math.min(limit, 15));
|
int max = Math.max(5, Math.min(limit, 15));
|
||||||
LinkedHashSet<String> names = new LinkedHashSet<>();
|
LinkedHashSet<String> names = new LinkedHashSet<>();
|
||||||
|
|
||||||
if (researchPack != null) {
|
if (researchPack != null) {
|
||||||
@@ -1308,7 +1438,7 @@ public class MarketingAnalysisService {
|
|||||||
|
|
||||||
MarketingAnalysisResponseV2.MarketOverviewMetrics moMetrics = new MarketingAnalysisResponseV2.MarketOverviewMetrics();
|
MarketingAnalysisResponseV2.MarketOverviewMetrics moMetrics = new MarketingAnalysisResponseV2.MarketOverviewMetrics();
|
||||||
moMetrics.setOnlineDemandIndex("Высокий (8/10)");
|
moMetrics.setOnlineDemandIndex("Высокий (8/10)");
|
||||||
moMetrics.setOnlinePotential("Растущий тренд (+15-20% YoY)");
|
moMetrics.setOnlinePotential("Растущий тренд )");
|
||||||
moMetrics.setDemandGrowthRateYoy("+18.5%");
|
moMetrics.setDemandGrowthRateYoy("+18.5%");
|
||||||
moMetrics.setSeasonalityCoefficientSpring("1.25 (Высокий сезон)");
|
moMetrics.setSeasonalityCoefficientSpring("1.25 (Высокий сезон)");
|
||||||
moMetrics.setSeasonalityRange("Сентябрь - Май");
|
moMetrics.setSeasonalityRange("Сентябрь - Май");
|
||||||
|
|||||||
Reference in New Issue
Block a user